@aztec/wallet-sdk 0.0.1-commit.d1f2d6c → 0.0.1-commit.d431d1c

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 (67) hide show
  1. package/README.md +294 -217
  2. package/dest/base-wallet/base_wallet.d.ts +4 -4
  3. package/dest/base-wallet/base_wallet.d.ts.map +1 -1
  4. package/dest/base-wallet/base_wallet.js +4 -9
  5. package/dest/crypto.d.ts +50 -59
  6. package/dest/crypto.d.ts.map +1 -1
  7. package/dest/crypto.js +108 -202
  8. package/dest/manager/index.d.ts +8 -2
  9. package/dest/manager/index.d.ts.map +1 -1
  10. package/dest/manager/index.js +6 -0
  11. package/dest/manager/types.d.ts +6 -88
  12. package/dest/manager/types.d.ts.map +1 -1
  13. package/dest/manager/types.js +1 -17
  14. package/dest/manager/wallet_manager.d.ts +7 -50
  15. package/dest/manager/wallet_manager.d.ts.map +1 -1
  16. package/dest/manager/wallet_manager.js +44 -174
  17. package/dest/providers/extension/extension_provider.d.ts +63 -0
  18. package/dest/providers/extension/extension_provider.d.ts.map +1 -0
  19. package/dest/providers/extension/extension_provider.js +124 -0
  20. package/dest/providers/extension/extension_wallet.d.ts +155 -0
  21. package/dest/providers/extension/extension_wallet.d.ts.map +1 -0
  22. package/dest/{extension/provider → providers/extension}/extension_wallet.js +95 -48
  23. package/dest/providers/extension/index.d.ts +6 -0
  24. package/dest/providers/extension/index.d.ts.map +1 -0
  25. package/dest/{extension/provider → providers/extension}/index.js +2 -0
  26. package/dest/types.d.ts +12 -43
  27. package/dest/types.d.ts.map +1 -1
  28. package/dest/types.js +2 -3
  29. package/package.json +9 -10
  30. package/src/base-wallet/base_wallet.ts +8 -15
  31. package/src/crypto.ts +113 -237
  32. package/src/manager/index.ts +10 -2
  33. package/src/manager/types.ts +5 -91
  34. package/src/manager/wallet_manager.ts +46 -192
  35. package/src/providers/extension/extension_provider.ts +167 -0
  36. package/src/{extension/provider → providers/extension}/extension_wallet.ts +110 -52
  37. package/src/providers/extension/index.ts +5 -0
  38. package/src/types.ts +10 -44
  39. package/dest/emoji_alphabet.d.ts +0 -35
  40. package/dest/emoji_alphabet.d.ts.map +0 -1
  41. package/dest/emoji_alphabet.js +0 -299
  42. package/dest/extension/handlers/background_connection_handler.d.ts +0 -158
  43. package/dest/extension/handlers/background_connection_handler.d.ts.map +0 -1
  44. package/dest/extension/handlers/background_connection_handler.js +0 -258
  45. package/dest/extension/handlers/content_script_connection_handler.d.ts +0 -56
  46. package/dest/extension/handlers/content_script_connection_handler.d.ts.map +0 -1
  47. package/dest/extension/handlers/content_script_connection_handler.js +0 -174
  48. package/dest/extension/handlers/index.d.ts +0 -12
  49. package/dest/extension/handlers/index.d.ts.map +0 -1
  50. package/dest/extension/handlers/index.js +0 -10
  51. package/dest/extension/handlers/internal_message_types.d.ts +0 -63
  52. package/dest/extension/handlers/internal_message_types.d.ts.map +0 -1
  53. package/dest/extension/handlers/internal_message_types.js +0 -22
  54. package/dest/extension/provider/extension_provider.d.ts +0 -107
  55. package/dest/extension/provider/extension_provider.d.ts.map +0 -1
  56. package/dest/extension/provider/extension_provider.js +0 -160
  57. package/dest/extension/provider/extension_wallet.d.ts +0 -131
  58. package/dest/extension/provider/extension_wallet.d.ts.map +0 -1
  59. package/dest/extension/provider/index.d.ts +0 -3
  60. package/dest/extension/provider/index.d.ts.map +0 -1
  61. package/src/emoji_alphabet.ts +0 -317
  62. package/src/extension/handlers/background_connection_handler.ts +0 -423
  63. package/src/extension/handlers/content_script_connection_handler.ts +0 -246
  64. package/src/extension/handlers/index.ts +0 -25
  65. package/src/extension/handlers/internal_message_types.ts +0 -69
  66. package/src/extension/provider/extension_provider.ts +0 -233
  67. package/src/extension/provider/index.ts +0 -7
@@ -6,7 +6,7 @@ import { schemaHasMethod } from '@aztec/foundation/schemas';
6
6
  import type { FunctionsOf } from '@aztec/foundation/types';
7
7
 
8
8
  import { type EncryptedPayload, decrypt, encrypt } from '../../crypto.js';
9
- import { type WalletMessage, WalletMessageType, type WalletResponse } from '../../types.js';
9
+ import { type WalletInfo, type WalletMessage, WalletMessageType, type WalletResponse } from '../../types.js';
10
10
 
11
11
  /**
12
12
  * Internal type representing a wallet method call before encryption.
@@ -26,30 +26,30 @@ export type DisconnectCallback = () => void;
26
26
 
27
27
  /**
28
28
  * A wallet implementation that communicates with browser extension wallets
29
- * using an encrypted MessageChannel.
29
+ * using a secure encrypted MessageChannel.
30
30
  *
31
- * This class uses a secure channel established after discovery:
31
+ * This class uses a pre-established secure channel from the discovery phase:
32
32
  *
33
- * 1. **MessageChannel**: Created during discovery and transferred via window.postMessage.
34
- * Note: The port transfer is visible to page scripts, but security comes from encryption.
33
+ * 1. **MessageChannel**: A private communication channel created during discovery,
34
+ * not visible to other scripts on the page (unlike window.postMessage).
35
35
  *
36
- * 2. **ECDH Key Exchange**: The shared secret was derived after discovery using
37
- * Elliptic Curve Diffie-Hellman key exchange over the MessagePort.
36
+ * 2. **ECDH Key Exchange**: The shared secret was derived during discovery using
37
+ * Elliptic Curve Diffie-Hellman key exchange.
38
38
  *
39
39
  * 3. **AES-GCM Encryption**: All messages are encrypted using AES-256-GCM,
40
- * providing both confidentiality and authenticity. This is what secures the channel.
40
+ * providing both confidentiality and authenticity.
41
41
  *
42
42
  * @example
43
43
  * ```typescript
44
- * // Discover and establish secure channel to a wallet
45
- * const discoveredWallets = await ExtensionProvider.discoverWallets(chainInfo, { appId: 'my-dapp' });
46
- * const connection = await discoveredWallets[0].establishSecureChannel();
44
+ * // Discovery returns wallets with secure channel components
45
+ * const wallets = await ExtensionProvider.discoverExtensions(chainInfo);
46
+ * const { info, port, sharedKey } = wallets[0];
47
47
  *
48
48
  * // User can verify emoji if desired
49
- * console.log('Verify:', hashToEmoji(connection.info.verificationHash!));
49
+ * console.log('Verify:', hashToEmoji(info.verificationHash!));
50
50
  *
51
- * // Create wallet using the connection
52
- * const wallet = ExtensionWallet.create(connection.info.id, connection.port, connection.sharedKey, chainInfo, 'my-dapp');
51
+ * // Create wallet using the discovered components
52
+ * const wallet = await ExtensionWallet.create(info, chainInfo, port, sharedKey, 'my-dapp');
53
53
  *
54
54
  * // All subsequent calls are encrypted
55
55
  * const accounts = await wallet.getAccounts();
@@ -77,56 +77,81 @@ export class ExtensionWallet {
77
77
  private sharedKey: CryptoKey,
78
78
  ) {}
79
79
 
80
+ /** Cached Wallet proxy instance */
81
+ private walletProxy: Wallet | null = null;
82
+
80
83
  /**
81
- * Creates a Wallet that communicates with a browser extension
84
+ * Creates an ExtensionWallet instance that communicates with a browser extension
82
85
  * over a secure encrypted MessageChannel.
83
86
  *
84
- * @param extensionId - The unique identifier of the wallet extension
85
- * @param port - The MessagePort for encrypted communication with the wallet
86
- * @param sharedKey - The derived AES-256-GCM shared key for encryption
87
+ * @param walletInfo - The wallet info from ExtensionProvider.discoverExtensions()
87
88
  * @param chainInfo - The chain information (chainId and version) for request context
89
+ * @param port - The MessagePort for private communication with the wallet
90
+ * @param sharedKey - The derived AES-256-GCM shared key for encryption
88
91
  * @param appId - Application identifier used to identify the requesting dApp to the wallet
89
- * @returns A Wallet interface where all method calls are encrypted
92
+ * @returns The ExtensionWallet instance. Use {@link getWallet} to get the Wallet interface.
90
93
  *
91
94
  * @example
92
95
  * ```typescript
93
- * const discoveredWallets = await ExtensionProvider.discoverWallets(chainInfo, { appId: 'my-defi-app' });
94
- * const connection = await discoveredWallets[0].establishSecureChannel();
95
- * const wallet = ExtensionWallet.create(
96
- * connection.info.id,
97
- * connection.port,
98
- * connection.sharedKey,
99
- * chainInfo,
96
+ * const wallets = await ExtensionProvider.discoverExtensions(chainInfo);
97
+ * const { info, port, sharedKey } = wallets[0];
98
+ * const extensionWallet = ExtensionWallet.create(
99
+ * info,
100
+ * { chainId: Fr(31337), version: Fr(0) },
101
+ * port,
102
+ * sharedKey,
100
103
  * 'my-defi-app'
101
104
  * );
102
105
  *
106
+ * // Register disconnect handler
107
+ * extensionWallet.onDisconnect(() => console.log('Disconnected!'));
108
+ *
109
+ * // Get the Wallet interface for dApp usage
110
+ * const wallet = extensionWallet.getWallet();
103
111
  * const accounts = await wallet.getAccounts();
104
112
  * ```
105
113
  */
106
114
  static create(
107
- extensionId: string,
115
+ walletInfo: WalletInfo,
116
+ chainInfo: ChainInfo,
108
117
  port: MessagePort,
109
118
  sharedKey: CryptoKey,
110
- chainInfo: ChainInfo,
111
119
  appId: string,
112
- ): Wallet {
113
- const wallet = new ExtensionWallet(chainInfo, appId, extensionId, port, sharedKey);
114
-
115
- // Set up message handler for encrypted responses and unencrypted control messages
116
- wallet.port.onmessage = (event: MessageEvent) => {
117
- const data = event.data;
118
- // Check for unencrypted disconnect notification
119
- if (data && typeof data === 'object' && 'type' in data && data.type === WalletMessageType.DISCONNECT) {
120
- wallet.handleDisconnect();
121
- return;
122
- }
123
- // Otherwise treat as encrypted payload
124
- void wallet.handleEncryptedResponse(data as EncryptedPayload);
120
+ ): ExtensionWallet {
121
+ const wallet = new ExtensionWallet(chainInfo, appId, walletInfo.id, port, sharedKey);
122
+
123
+ // Set up message handler - all messages are now encrypted
124
+ wallet.port.onmessage = (event: MessageEvent<EncryptedPayload>) => {
125
+ void wallet.handleEncryptedResponse(event.data);
125
126
  };
126
127
 
127
128
  wallet.port.start();
128
129
 
129
- return new Proxy(wallet, {
130
+ return wallet;
131
+ }
132
+
133
+ /**
134
+ * Returns a Wallet interface that proxies all method calls through the secure channel.
135
+ *
136
+ * The returned Wallet can be used directly by dApps - all method calls are automatically
137
+ * encrypted and sent to the wallet extension.
138
+ *
139
+ * @returns A Wallet implementation that encrypts all communication
140
+ *
141
+ * @example
142
+ * ```typescript
143
+ * const extensionWallet = ExtensionWallet.create(info, chainInfo, port, sharedKey, 'my-app');
144
+ * const wallet = extensionWallet.getWallet();
145
+ * const accounts = await wallet.getAccounts();
146
+ * ```
147
+ */
148
+ getWallet(): Wallet {
149
+ if (this.walletProxy) {
150
+ return this.walletProxy;
151
+ }
152
+
153
+ // Create a Proxy that intercepts wallet method calls and forwards them to the extension
154
+ this.walletProxy = new Proxy(this, {
130
155
  get: (target, prop) => {
131
156
  if (schemaHasMethod(WalletSchema, prop.toString())) {
132
157
  return async (...args: unknown[]) => {
@@ -141,6 +166,8 @@ export class ExtensionWallet {
141
166
  }
142
167
  },
143
168
  }) as unknown as Wallet;
169
+
170
+ return this.walletProxy;
144
171
  }
145
172
 
146
173
  /**
@@ -161,6 +188,18 @@ export class ExtensionWallet {
161
188
 
162
189
  const { messageId, result, error, walletId: responseWalletId } = response;
163
190
 
191
+ // Check for disconnect notification from the wallet backend
192
+ // This is sent as an encrypted error response with a special type
193
+ if (
194
+ error &&
195
+ typeof error === 'object' &&
196
+ 'type' in error &&
197
+ (error.type as WalletMessageType) === WalletMessageType.SESSION_DISCONNECTED
198
+ ) {
199
+ this.handleDisconnect();
200
+ return;
201
+ }
202
+
164
203
  if (!messageId || !responseWalletId) {
165
204
  return;
166
205
  }
@@ -215,7 +254,8 @@ export class ExtensionWallet {
215
254
  walletId: this.extensionId,
216
255
  };
217
256
 
218
- const encrypted = await encrypt(this.sharedKey, jsonStringify(message));
257
+ // Encrypt the message and send over the private MessageChannel
258
+ const encrypted = await encrypt(this.sharedKey, message);
219
259
  this.port.postMessage(encrypted);
220
260
 
221
261
  const { promise, resolve, reject } = promiseWithResolvers<unknown>();
@@ -234,22 +274,27 @@ export class ExtensionWallet {
234
274
  }
235
275
  this.disconnected = true;
236
276
 
277
+ // Close the port to prevent any further messages
237
278
  if (this.port) {
238
279
  this.port.onmessage = null;
239
280
  this.port.close();
240
281
  }
241
282
 
283
+ // Reject all pending requests
284
+ // Note: These rejections should be caught by the callers, but we log them
285
+ // here to help with debugging if they become unhandled
242
286
  const error = new Error('Wallet disconnected');
243
287
  for (const { reject } of this.inFlight.values()) {
244
288
  reject(error);
245
289
  }
246
290
  this.inFlight.clear();
247
291
 
292
+ // Notify registered callbacks
248
293
  for (const callback of this.disconnectCallbacks) {
249
294
  try {
250
295
  callback();
251
296
  } catch {
252
- // Ignore errors on disconnect callbacks
297
+ // Ignore errors in callbacks
253
298
  }
254
299
  }
255
300
  }
@@ -298,24 +343,37 @@ export class ExtensionWallet {
298
343
  *
299
344
  * @example
300
345
  * ```typescript
301
- * const extensionWallet = ExtensionWallet.create(extensionId, port, sharedKey, chainInfo, 'my-app');
346
+ * const wallet = await provider.connect('my-app');
302
347
  * // ... use wallet ...
303
- * await extensionWallet.disconnect(); // Clean disconnect when done
348
+ * await wallet.disconnect(); // Clean disconnect when done
304
349
  * ```
305
350
  */
306
- // eslint-disable-next-line require-await -- async for interface compatibility
307
351
  async disconnect(): Promise<void> {
308
352
  if (this.disconnected) {
309
353
  return;
310
354
  }
311
355
 
312
- if (this.port) {
313
- // Send unencrypted disconnect - control messages don't need encryption
314
- this.port.postMessage({
315
- type: WalletMessageType.DISCONNECT,
316
- });
356
+ // Send disconnect message to extension before closing
357
+ if (this.port && this.sharedKey) {
358
+ try {
359
+ const message = {
360
+ type: WalletMessageType.DISCONNECT,
361
+ messageId: globalThis.crypto.randomUUID(),
362
+ chainInfo: this.chainInfo,
363
+ appId: this.appId,
364
+ walletId: this.extensionId,
365
+ args: [],
366
+ };
367
+ const encrypted = await encrypt(this.sharedKey, message);
368
+ this.port.postMessage(encrypted);
369
+ } catch {
370
+ // Ignore errors sending disconnect message
371
+ }
317
372
  }
318
373
 
319
374
  this.handleDisconnect();
375
+ if (this.port) {
376
+ this.port.close();
377
+ }
320
378
  }
321
379
  }
@@ -0,0 +1,5 @@
1
+ export { ExtensionWallet, type DisconnectCallback } from './extension_wallet.js';
2
+ export { ExtensionProvider, type DiscoveredWallet } from './extension_provider.js';
3
+ export * from '../../crypto.js';
4
+ export { WalletMessageType } from '../../types.js';
5
+ export type { WalletInfo, WalletMessage, WalletResponse, DiscoveryRequest, DiscoveryResponse } from '../../types.js';
package/src/types.ts CHANGED
@@ -11,17 +11,14 @@ export enum WalletMessageType {
11
11
  DISCOVERY = 'aztec-wallet-discovery',
12
12
  /** Discovery response from a wallet */
13
13
  DISCOVERY_RESPONSE = 'aztec-wallet-discovery-response',
14
- /** Disconnect message (unencrypted control message, bidirectional) */
14
+ /** Session disconnected notification (unencrypted control message) */
15
+ SESSION_DISCONNECTED = 'aztec-wallet-session-disconnected',
16
+ /** Explicit disconnect request from dApp */
15
17
  DISCONNECT = 'aztec-wallet-disconnect',
16
- /** Key exchange request sent over MessageChannel */
17
- KEY_EXCHANGE_REQUEST = 'aztec-wallet-key-exchange-request',
18
- /** Key exchange response sent over MessageChannel */
19
- KEY_EXCHANGE_RESPONSE = 'aztec-wallet-key-exchange-response',
20
18
  }
21
19
 
22
20
  /**
23
- * Information about an installed Aztec wallet.
24
- * Used during discovery phase before key exchange.
21
+ * Information about an installed Aztec wallet
25
22
  */
26
23
  export interface WalletInfo {
27
24
  /** Unique identifier for the wallet */
@@ -32,17 +29,10 @@ export interface WalletInfo {
32
29
  icon?: string;
33
30
  /** Wallet version */
34
31
  version: string;
35
- }
36
-
37
- /**
38
- * Full information about a connected Aztec wallet including crypto material.
39
- * Available after key exchange completes.
40
- */
41
- export interface ConnectedWalletInfo extends WalletInfo {
42
32
  /** Wallet's ECDH public key for secure channel establishment */
43
33
  publicKey: ExportedPublicKey;
44
34
  /**
45
- * Verification hash for verification.
35
+ * Hash of the shared secret for anti-MITM verification.
46
36
  * Both dApp and wallet independently compute this from the ECDH shared secret.
47
37
  * Use {@link hashToEmoji} to convert to a visual representation for user verification.
48
38
  */
@@ -82,51 +72,27 @@ export interface WalletResponse {
82
72
  }
83
73
 
84
74
  /**
85
- * Discovery message for finding installed wallets (public, unencrypted).
75
+ * Discovery message for finding installed wallets (public, unencrypted)
86
76
  */
87
77
  export interface DiscoveryRequest {
88
78
  /** Message type for discovery */
89
79
  type: WalletMessageType.DISCOVERY;
90
80
  /** Request ID */
91
81
  requestId: string;
92
- /** Application ID making the request */
93
- appId: string;
94
82
  /** Chain information to check if wallet supports this network */
95
83
  chainInfo: ChainInfo;
84
+ /** dApp's ECDH public key for deriving shared secret */
85
+ publicKey: ExportedPublicKey;
96
86
  }
97
87
 
98
88
  /**
99
- * Discovery response from a wallet (public, unencrypted).
89
+ * Discovery response from a wallet (public, unencrypted)
100
90
  */
101
91
  export interface DiscoveryResponse {
102
92
  /** Message type for discovery response */
103
93
  type: WalletMessageType.DISCOVERY_RESPONSE;
104
94
  /** Request ID matching the discovery request */
105
95
  requestId: string;
106
- /** Basic wallet information */
96
+ /** Wallet information */
107
97
  walletInfo: WalletInfo;
108
98
  }
109
-
110
- /**
111
- * Key exchange request sent over MessageChannel after discovery approval.
112
- */
113
- export interface KeyExchangeRequest {
114
- /** Message type */
115
- type: WalletMessageType.KEY_EXCHANGE_REQUEST;
116
- /** Request ID matching the discovery request */
117
- requestId: string;
118
- /** dApp's ECDH public key for deriving shared secret */
119
- publicKey: ExportedPublicKey;
120
- }
121
-
122
- /**
123
- * Key exchange response sent over MessageChannel.
124
- */
125
- export interface KeyExchangeResponse {
126
- /** Message type */
127
- type: WalletMessageType.KEY_EXCHANGE_RESPONSE;
128
- /** Request ID matching the discovery request */
129
- requestId: string;
130
- /** Wallet's ECDH public key for deriving shared secret */
131
- publicKey: ExportedPublicKey;
132
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Emoji alphabet for visual verification of shared secrets.
3
- *
4
- * This alphabet contains 256 distinct, easily recognizable emojis for
5
- * anti-spoofing verification. With 256 emojis, each emoji represents
6
- * exactly 8 bits of entropy (log2(256) = 8).
7
- *
8
- * A 3x3 grid of 9 emojis provides 72 bits of security (9 × 8 = 72 bits),
9
- * making brute-force attacks computationally infeasible for real-time
10
- * MITM verification scenarios.
11
- *
12
- * Selection criteria:
13
- * - Visually distinct from each other
14
- * - Commonly supported across platforms (Unicode 12+)
15
- * - Easy to recognize and compare at a glance
16
- * - No text-based or flag emojis (platform-dependent rendering)
17
- * - No variation selectors (base emojis only)
18
- *
19
- * @packageDocumentation
20
- */
21
- /**
22
- * 256 distinct emojis for visual verification.
23
- * Index directly maps to byte value (0-255) for simple lookup.
24
- */
25
- export declare const EMOJI_ALPHABET: readonly string[];
26
- /**
27
- * Number of emojis in the alphabet.
28
- * Must be a power of 2 for clean bit mapping (256 = 2^8).
29
- */
30
- export declare const EMOJI_ALPHABET_SIZE = 256;
31
- /**
32
- * Bits of entropy per emoji (log2 of alphabet size).
33
- */
34
- export declare const BITS_PER_EMOJI = 8;
35
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW1vamlfYWxwaGFiZXQuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9lbW9qaV9hbHBoYWJldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRztBQUVIOzs7R0FHRztBQUNILGVBQU8sTUFBTSxjQUFjLEVBQUUsU0FBUyxNQUFNLEVBd1JsQyxDQUFDO0FBRVg7OztHQUdHO0FBQ0gsZUFBTyxNQUFNLG1CQUFtQixNQUFNLENBQUM7QUFFdkM7O0dBRUc7QUFDSCxlQUFPLE1BQU0sY0FBYyxJQUFJLENBQUMifQ==
@@ -1 +0,0 @@
1
- {"version":3,"file":"emoji_alphabet.d.ts","sourceRoot":"","sources":["../src/emoji_alphabet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAwRlC,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC;;GAEG;AACH,eAAO,MAAM,cAAc,IAAI,CAAC"}