@aztec/wallet-sdk 0.0.1-commit.7d4e6cd → 0.0.1-commit.9372f48

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 (69) hide show
  1. package/README.md +218 -355
  2. package/dest/base-wallet/base_wallet.d.ts +33 -10
  3. package/dest/base-wallet/base_wallet.d.ts.map +1 -1
  4. package/dest/base-wallet/base_wallet.js +53 -16
  5. package/dest/crypto.d.ts +73 -27
  6. package/dest/crypto.d.ts.map +1 -1
  7. package/dest/crypto.js +219 -41
  8. package/dest/emoji_alphabet.d.ts +35 -0
  9. package/dest/emoji_alphabet.d.ts.map +1 -0
  10. package/dest/emoji_alphabet.js +299 -0
  11. package/dest/extension/handlers/background_connection_handler.d.ts +158 -0
  12. package/dest/extension/handlers/background_connection_handler.d.ts.map +1 -0
  13. package/dest/extension/handlers/background_connection_handler.js +258 -0
  14. package/dest/extension/handlers/content_script_connection_handler.d.ts +56 -0
  15. package/dest/extension/handlers/content_script_connection_handler.d.ts.map +1 -0
  16. package/dest/extension/handlers/content_script_connection_handler.js +174 -0
  17. package/dest/extension/handlers/index.d.ts +12 -0
  18. package/dest/extension/handlers/index.d.ts.map +1 -0
  19. package/dest/extension/handlers/index.js +10 -0
  20. package/dest/extension/handlers/internal_message_types.d.ts +63 -0
  21. package/dest/extension/handlers/internal_message_types.d.ts.map +1 -0
  22. package/dest/extension/handlers/internal_message_types.js +22 -0
  23. package/dest/extension/provider/extension_provider.d.ts +107 -0
  24. package/dest/extension/provider/extension_provider.d.ts.map +1 -0
  25. package/dest/extension/provider/extension_provider.js +160 -0
  26. package/dest/extension/provider/extension_wallet.d.ts +131 -0
  27. package/dest/extension/provider/extension_wallet.d.ts.map +1 -0
  28. package/dest/extension/provider/extension_wallet.js +271 -0
  29. package/dest/extension/provider/index.d.ts +3 -0
  30. package/dest/extension/provider/index.d.ts.map +1 -0
  31. package/dest/{providers/extension → extension/provider}/index.js +0 -1
  32. package/dest/manager/index.d.ts +2 -7
  33. package/dest/manager/index.d.ts.map +1 -1
  34. package/dest/manager/index.js +0 -4
  35. package/dest/manager/types.d.ts +108 -5
  36. package/dest/manager/types.d.ts.map +1 -1
  37. package/dest/manager/types.js +17 -1
  38. package/dest/manager/wallet_manager.d.ts +50 -7
  39. package/dest/manager/wallet_manager.d.ts.map +1 -1
  40. package/dest/manager/wallet_manager.js +178 -29
  41. package/dest/types.d.ts +55 -15
  42. package/dest/types.d.ts.map +1 -1
  43. package/dest/types.js +10 -2
  44. package/package.json +11 -10
  45. package/src/base-wallet/base_wallet.ts +74 -28
  46. package/src/crypto.ts +263 -47
  47. package/src/emoji_alphabet.ts +317 -0
  48. package/src/extension/handlers/background_connection_handler.ts +423 -0
  49. package/src/extension/handlers/content_script_connection_handler.ts +246 -0
  50. package/src/extension/handlers/index.ts +25 -0
  51. package/src/extension/handlers/internal_message_types.ts +69 -0
  52. package/src/extension/provider/extension_provider.ts +233 -0
  53. package/src/extension/provider/extension_wallet.ts +321 -0
  54. package/src/extension/provider/index.ts +7 -0
  55. package/src/manager/index.ts +3 -9
  56. package/src/manager/types.ts +112 -4
  57. package/src/manager/wallet_manager.ts +204 -31
  58. package/src/types.ts +57 -14
  59. package/dest/providers/extension/extension_provider.d.ts +0 -17
  60. package/dest/providers/extension/extension_provider.d.ts.map +0 -1
  61. package/dest/providers/extension/extension_provider.js +0 -56
  62. package/dest/providers/extension/extension_wallet.d.ts +0 -95
  63. package/dest/providers/extension/extension_wallet.d.ts.map +0 -1
  64. package/dest/providers/extension/extension_wallet.js +0 -225
  65. package/dest/providers/extension/index.d.ts +0 -5
  66. package/dest/providers/extension/index.d.ts.map +0 -1
  67. package/src/providers/extension/extension_provider.ts +0 -72
  68. package/src/providers/extension/extension_wallet.ts +0 -275
  69. package/src/providers/extension/index.ts +0 -11
@@ -1,95 +0,0 @@
1
- import type { ChainInfo } from '@aztec/aztec.js/account';
2
- import { type Wallet } from '@aztec/aztec.js/wallet';
3
- import type { WalletInfo } from '../../types.js';
4
- /**
5
- * A wallet implementation that communicates with browser extension wallets
6
- * using a secure encrypted MessageChannel.
7
- *
8
- * This class establishes a private communication channel with a wallet extension
9
- * using the following security mechanisms:
10
- *
11
- * 1. **MessageChannel**: Creates a private communication channel that is not
12
- * visible to other scripts on the page (unlike window.postMessage).
13
- *
14
- * 2. **ECDH Key Exchange**: Uses Elliptic Curve Diffie-Hellman to derive a
15
- * shared secret between the dApp and wallet without transmitting private keys.
16
- *
17
- * 3. **AES-GCM Encryption**: All messages after channel establishment are
18
- * encrypted using AES-256-GCM, providing both confidentiality and authenticity.
19
- *
20
- * @example
21
- * ```typescript
22
- * // Discovery returns wallet info including the wallet's public key
23
- * const wallets = await ExtensionProvider.discoverExtensions(chainInfo);
24
- * const walletInfo = wallets[0];
25
- *
26
- * // Create a secure connection to the wallet
27
- * const wallet = await ExtensionWallet.create(walletInfo, chainInfo, 'my-dapp');
28
- *
29
- * // All subsequent calls are encrypted
30
- * const accounts = await wallet.getAccounts();
31
- * ```
32
- */
33
- export declare class ExtensionWallet {
34
- private chainInfo;
35
- private appId;
36
- private extensionId;
37
- /** Map of pending requests awaiting responses, keyed by message ID */
38
- private inFlight;
39
- /** The MessagePort for private communication with the extension */
40
- private port;
41
- /** The derived AES-GCM key for encrypting/decrypting messages */
42
- private sharedKey;
43
- /**
44
- * Private constructor - use {@link ExtensionWallet.create} to instantiate.
45
- * @param chainInfo - The chain information (chainId and version)
46
- * @param appId - Application identifier for the requesting dApp
47
- * @param extensionId - The unique identifier of the target wallet extension
48
- */
49
- private constructor();
50
- /**
51
- * Creates an ExtensionWallet instance that proxies wallet calls to a browser extension
52
- * over a secure encrypted MessageChannel.
53
- *
54
- * The connection process:
55
- * 1. Generates an ECDH key pair for this session
56
- * 2. Derives a shared AES-256 key using the wallet's public key
57
- * 3. Creates a MessageChannel and transfers one port to the extension
58
- * 4. Returns a Proxy that encrypts all wallet method calls
59
- *
60
- * @param walletInfo - The discovered wallet information, including the wallet's ECDH public key
61
- * @param chainInfo - The chain information (chainId and version) for request context
62
- * @param appId - Application identifier used to identify the requesting dApp to the wallet
63
- * @returns A Promise resolving to a Wallet implementation that encrypts all communication
64
- *
65
- * @throws Error if the secure channel cannot be established
66
- *
67
- * @example
68
- * ```typescript
69
- * const wallet = await ExtensionWallet.create(
70
- * walletInfo,
71
- * { chainId: Fr(31337), version: Fr(0) },
72
- * 'my-defi-app'
73
- * );
74
- * ```
75
- */
76
- static create(walletInfo: WalletInfo, chainInfo: ChainInfo, appId: string): Promise<Wallet>;
77
- private establishSecureChannel;
78
- private handleEncryptedResponse;
79
- private postMessage;
80
- /**
81
- * Closes the secure channel and cleans up resources.
82
- *
83
- * After calling this method, the wallet instance can no longer be used.
84
- * Any pending requests will not receive responses.
85
- *
86
- * @example
87
- * ```typescript
88
- * const wallet = await ExtensionWallet.create(walletInfo, chainInfo, 'my-app');
89
- * // ... use wallet ...
90
- * wallet.close(); // Clean up when done
91
- * ```
92
- */
93
- close(): void;
94
- }
95
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXh0ZW5zaW9uX3dhbGxldC5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3Byb3ZpZGVycy9leHRlbnNpb24vZXh0ZW5zaW9uX3dhbGxldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxTQUFTLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN6RCxPQUFPLEVBQUUsS0FBSyxNQUFNLEVBQWdCLE1BQU0sd0JBQXdCLENBQUM7QUFnQm5FLE9BQU8sS0FBSyxFQUFrQixVQUFVLEVBQWlDLE1BQU0sZ0JBQWdCLENBQUM7QUFhaEc7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E0Qkc7QUFDSCxxQkFBYSxlQUFlO0lBaUJ4QixPQUFPLENBQUMsU0FBUztJQUNqQixPQUFPLENBQUMsS0FBSztJQUNiLE9BQU8sQ0FBQyxXQUFXO0lBbEJyQixzRUFBc0U7SUFDdEUsT0FBTyxDQUFDLFFBQVEsQ0FBb0Q7SUFFcEUsbUVBQW1FO0lBQ25FLE9BQU8sQ0FBQyxJQUFJLENBQTRCO0lBRXhDLGlFQUFpRTtJQUNqRSxPQUFPLENBQUMsU0FBUyxDQUEwQjtJQUUzQzs7Ozs7T0FLRztJQUNILE9BQU8sZUFJSDtJQUVKOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O09BeUJHO0lBQ0gsT0FBYSxNQUFNLENBQUMsVUFBVSxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQXlCaEc7WUFjYSxzQkFBc0I7WUFxQ3RCLHVCQUF1QjtZQThDdkIsV0FBVztJQXdCekI7Ozs7Ozs7Ozs7OztPQVlHO0lBQ0gsS0FBSyxJQUFJLElBQUksQ0FPWjtDQUNGIn0=
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension_wallet.d.ts","sourceRoot":"","sources":["../../../src/providers/extension/extension_wallet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,wBAAwB,CAAC;AAgBnE,OAAO,KAAK,EAAkB,UAAU,EAAiC,MAAM,gBAAgB,CAAC;AAahG;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,eAAe;IAiBxB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,WAAW;IAlBrB,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAoD;IAEpE,mEAAmE;IACnE,OAAO,CAAC,IAAI,CAA4B;IAExC,iEAAiE;IACjE,OAAO,CAAC,SAAS,CAA0B;IAE3C;;;;;OAKG;IACH,OAAO,eAIH;IAEJ;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,OAAa,MAAM,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBhG;YAca,sBAAsB;YAqCtB,uBAAuB;YA8CvB,WAAW;IAwBzB;;;;;;;;;;;;OAYG;IACH,KAAK,IAAI,IAAI,CAOZ;CACF"}
@@ -1,225 +0,0 @@
1
- import { WalletSchema } from '@aztec/aztec.js/wallet';
2
- import { jsonStringify } from '@aztec/foundation/json-rpc';
3
- import { promiseWithResolvers } from '@aztec/foundation/promise';
4
- import { schemaHasMethod } from '@aztec/foundation/schemas';
5
- import { decrypt, deriveSharedKey, encrypt, exportPublicKey, generateKeyPair, importPublicKey } from '../../crypto.js';
6
- /**
7
- * A wallet implementation that communicates with browser extension wallets
8
- * using a secure encrypted MessageChannel.
9
- *
10
- * This class establishes a private communication channel with a wallet extension
11
- * using the following security mechanisms:
12
- *
13
- * 1. **MessageChannel**: Creates a private communication channel that is not
14
- * visible to other scripts on the page (unlike window.postMessage).
15
- *
16
- * 2. **ECDH Key Exchange**: Uses Elliptic Curve Diffie-Hellman to derive a
17
- * shared secret between the dApp and wallet without transmitting private keys.
18
- *
19
- * 3. **AES-GCM Encryption**: All messages after channel establishment are
20
- * encrypted using AES-256-GCM, providing both confidentiality and authenticity.
21
- *
22
- * @example
23
- * ```typescript
24
- * // Discovery returns wallet info including the wallet's public key
25
- * const wallets = await ExtensionProvider.discoverExtensions(chainInfo);
26
- * const walletInfo = wallets[0];
27
- *
28
- * // Create a secure connection to the wallet
29
- * const wallet = await ExtensionWallet.create(walletInfo, chainInfo, 'my-dapp');
30
- *
31
- * // All subsequent calls are encrypted
32
- * const accounts = await wallet.getAccounts();
33
- * ```
34
- */ export class ExtensionWallet {
35
- chainInfo;
36
- appId;
37
- extensionId;
38
- /** Map of pending requests awaiting responses, keyed by message ID */ inFlight;
39
- /** The MessagePort for private communication with the extension */ port;
40
- /** The derived AES-GCM key for encrypting/decrypting messages */ sharedKey;
41
- /**
42
- * Private constructor - use {@link ExtensionWallet.create} to instantiate.
43
- * @param chainInfo - The chain information (chainId and version)
44
- * @param appId - Application identifier for the requesting dApp
45
- * @param extensionId - The unique identifier of the target wallet extension
46
- */ constructor(chainInfo, appId, extensionId){
47
- this.chainInfo = chainInfo;
48
- this.appId = appId;
49
- this.extensionId = extensionId;
50
- this.inFlight = new Map();
51
- this.port = null;
52
- this.sharedKey = null;
53
- }
54
- /**
55
- * Creates an ExtensionWallet instance that proxies wallet calls to a browser extension
56
- * over a secure encrypted MessageChannel.
57
- *
58
- * The connection process:
59
- * 1. Generates an ECDH key pair for this session
60
- * 2. Derives a shared AES-256 key using the wallet's public key
61
- * 3. Creates a MessageChannel and transfers one port to the extension
62
- * 4. Returns a Proxy that encrypts all wallet method calls
63
- *
64
- * @param walletInfo - The discovered wallet information, including the wallet's ECDH public key
65
- * @param chainInfo - The chain information (chainId and version) for request context
66
- * @param appId - Application identifier used to identify the requesting dApp to the wallet
67
- * @returns A Promise resolving to a Wallet implementation that encrypts all communication
68
- *
69
- * @throws Error if the secure channel cannot be established
70
- *
71
- * @example
72
- * ```typescript
73
- * const wallet = await ExtensionWallet.create(
74
- * walletInfo,
75
- * { chainId: Fr(31337), version: Fr(0) },
76
- * 'my-defi-app'
77
- * );
78
- * ```
79
- */ static async create(walletInfo, chainInfo, appId) {
80
- const wallet = new ExtensionWallet(chainInfo, appId, walletInfo.id);
81
- if (!walletInfo.publicKey) {
82
- throw new Error('Wallet does not support secure channel establishment (missing public key)');
83
- }
84
- await wallet.establishSecureChannel(walletInfo.publicKey);
85
- // Create a Proxy that intercepts wallet method calls and forwards them to the extension
86
- return new Proxy(wallet, {
87
- get: (target, prop)=>{
88
- if (schemaHasMethod(WalletSchema, prop.toString())) {
89
- return async (...args)=>{
90
- const result = await target.postMessage({
91
- type: prop.toString(),
92
- args
93
- });
94
- return WalletSchema[prop.toString()].returnType().parseAsync(result);
95
- };
96
- } else {
97
- return target[prop];
98
- }
99
- }
100
- });
101
- }
102
- /**
103
- * Establishes a secure MessageChannel with ECDH key exchange.
104
- *
105
- * This method performs the cryptographic handshake:
106
- * 1. Generates a new ECDH P-256 key pair for this session
107
- * 2. Imports the wallet's public key and derives a shared secret
108
- * 3. Creates a MessageChannel for private communication
109
- * 4. Sends a connection request with our public key via window.postMessage
110
- * (this is the only public message - subsequent communication uses the private channel)
111
- *
112
- * @param walletExportedPublicKey - The wallet's ECDH public key in JWK format
113
- */ async establishSecureChannel(walletExportedPublicKey) {
114
- const keyPair = await generateKeyPair();
115
- const exportedPublicKey = await exportPublicKey(keyPair.publicKey);
116
- const walletPublicKey = await importPublicKey(walletExportedPublicKey);
117
- this.sharedKey = await deriveSharedKey(keyPair.privateKey, walletPublicKey);
118
- const channel = new MessageChannel();
119
- this.port = channel.port1;
120
- this.port.onmessage = async (event)=>{
121
- await this.handleEncryptedResponse(event.data);
122
- };
123
- this.port.start();
124
- // Send connection request with our public key and transfer port2 to content script
125
- // This is the only public postMessage - it contains our public key (safe to expose)
126
- // and transfers the MessagePort for subsequent private communication
127
- const connectRequest = {
128
- type: 'aztec-wallet-connect',
129
- walletId: this.extensionId,
130
- appId: this.appId,
131
- publicKey: exportedPublicKey
132
- };
133
- window.postMessage(jsonStringify(connectRequest), '*', [
134
- channel.port2
135
- ]);
136
- }
137
- /**
138
- * Handles an encrypted response received from the wallet extension.
139
- *
140
- * Decrypts the response using the shared AES key and resolves or rejects
141
- * the corresponding pending promise based on the response content.
142
- *
143
- * @param encrypted - The encrypted response from the wallet
144
- */ async handleEncryptedResponse(encrypted) {
145
- if (!this.sharedKey) {
146
- return;
147
- }
148
- try {
149
- const response = await decrypt(this.sharedKey, encrypted);
150
- const { messageId, result, error, walletId: responseWalletId } = response;
151
- if (!messageId || !responseWalletId) {
152
- return;
153
- }
154
- if (this.extensionId !== responseWalletId) {
155
- return;
156
- }
157
- if (!this.inFlight.has(messageId)) {
158
- return;
159
- }
160
- const { resolve, reject } = this.inFlight.get(messageId);
161
- if (error) {
162
- reject(new Error(jsonStringify(error)));
163
- } else {
164
- resolve(result);
165
- }
166
- this.inFlight.delete(messageId);
167
- // eslint-disable-next-line no-empty
168
- } catch {}
169
- }
170
- /**
171
- * Sends an encrypted wallet method call over the secure MessageChannel.
172
- *
173
- * The message is encrypted using AES-256-GCM with the shared key derived
174
- * during channel establishment. A unique message ID is generated to correlate
175
- * the response.
176
- *
177
- * @param call - The wallet method call containing method name and arguments
178
- * @returns A Promise that resolves with the decrypted result from the wallet
179
- *
180
- * @throws Error if the secure channel has not been established
181
- */ async postMessage(call) {
182
- if (!this.port || !this.sharedKey) {
183
- throw new Error('Secure channel not established');
184
- }
185
- const messageId = globalThis.crypto.randomUUID();
186
- const message = {
187
- type: call.type,
188
- args: call.args,
189
- messageId,
190
- chainInfo: this.chainInfo,
191
- appId: this.appId,
192
- walletId: this.extensionId
193
- };
194
- // Encrypt the message and send over the private MessageChannel
195
- const encrypted = await encrypt(this.sharedKey, message);
196
- this.port.postMessage(encrypted);
197
- const { promise, resolve, reject } = promiseWithResolvers();
198
- this.inFlight.set(messageId, {
199
- promise,
200
- resolve,
201
- reject
202
- });
203
- return promise;
204
- }
205
- /**
206
- * Closes the secure channel and cleans up resources.
207
- *
208
- * After calling this method, the wallet instance can no longer be used.
209
- * Any pending requests will not receive responses.
210
- *
211
- * @example
212
- * ```typescript
213
- * const wallet = await ExtensionWallet.create(walletInfo, chainInfo, 'my-app');
214
- * // ... use wallet ...
215
- * wallet.close(); // Clean up when done
216
- * ```
217
- */ close() {
218
- if (this.port) {
219
- this.port.close();
220
- this.port = null;
221
- }
222
- this.sharedKey = null;
223
- this.inFlight.clear();
224
- }
225
- }
@@ -1,5 +0,0 @@
1
- export { ExtensionWallet } from './extension_wallet.js';
2
- export { ExtensionProvider } from './extension_provider.js';
3
- export * from '../../crypto.js';
4
- export type { WalletInfo, WalletMessage, WalletResponse, DiscoveryRequest, DiscoveryResponse, ConnectRequest, } from '../../types.js';
5
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9wcm92aWRlcnMvZXh0ZW5zaW9uL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUN4RCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUM1RCxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLFlBQVksRUFDVixVQUFVLEVBQ1YsYUFBYSxFQUNiLGNBQWMsRUFDZCxnQkFBZ0IsRUFDaEIsaUJBQWlCLEVBQ2pCLGNBQWMsR0FDZixNQUFNLGdCQUFnQixDQUFDIn0=
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/extension/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,cAAc,iBAAiB,CAAC;AAChC,YAAY,EACV,UAAU,EACV,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,GACf,MAAM,gBAAgB,CAAC"}
@@ -1,72 +0,0 @@
1
- import type { ChainInfo } from '@aztec/aztec.js/account';
2
- import { jsonStringify } from '@aztec/foundation/json-rpc';
3
- import { promiseWithResolvers } from '@aztec/foundation/promise';
4
-
5
- import type { DiscoveryRequest, DiscoveryResponse, WalletInfo } from '../../types.js';
6
-
7
- /**
8
- * Provider for discovering and managing Aztec wallet extensions
9
- */
10
- export class ExtensionProvider {
11
- private static discoveredExtensions: Map<string, WalletInfo> = new Map();
12
- private static discoveryInProgress = false;
13
-
14
- /**
15
- * Discovers all installed Aztec wallet extensions
16
- * @param chainInfo - Chain information to check if extensions support this network
17
- * @param timeout - How long to wait for extensions to respond (ms)
18
- * @returns Array of discovered extension information
19
- */
20
- static async discoverExtensions(chainInfo: ChainInfo, timeout: number = 1000): Promise<WalletInfo[]> {
21
- // If discovery is in progress, wait for it to complete
22
- if (this.discoveryInProgress) {
23
- await new Promise(resolve => setTimeout(resolve, timeout));
24
- return Array.from(this.discoveredExtensions.values());
25
- }
26
-
27
- this.discoveryInProgress = true;
28
- this.discoveredExtensions.clear();
29
-
30
- const { promise, resolve } = promiseWithResolvers<WalletInfo[]>();
31
- const requestId = globalThis.crypto.randomUUID();
32
- const responses: WalletInfo[] = [];
33
-
34
- // Set up listener for discovery responses
35
- const handleMessage = (event: MessageEvent) => {
36
- if (event.source !== window) {
37
- return;
38
- }
39
-
40
- let data: DiscoveryResponse;
41
- try {
42
- data = JSON.parse(event.data);
43
- } catch {
44
- return;
45
- }
46
-
47
- if (data.type === 'aztec-wallet-discovery-response' && data.requestId === requestId) {
48
- responses.push(data.walletInfo);
49
- this.discoveredExtensions.set(data.walletInfo.id, data.walletInfo);
50
- }
51
- };
52
-
53
- window.addEventListener('message', handleMessage);
54
-
55
- // Send discovery message
56
- const discoveryMessage: DiscoveryRequest = {
57
- type: 'aztec-wallet-discovery',
58
- requestId,
59
- chainInfo,
60
- };
61
- window.postMessage(jsonStringify(discoveryMessage), '*');
62
-
63
- // Wait for responses
64
- setTimeout(() => {
65
- window.removeEventListener('message', handleMessage);
66
- this.discoveryInProgress = false;
67
- resolve(responses);
68
- }, timeout);
69
-
70
- return promise;
71
- }
72
- }
@@ -1,275 +0,0 @@
1
- import type { ChainInfo } from '@aztec/aztec.js/account';
2
- import { type Wallet, WalletSchema } from '@aztec/aztec.js/wallet';
3
- import { jsonStringify } from '@aztec/foundation/json-rpc';
4
- import { type PromiseWithResolvers, promiseWithResolvers } from '@aztec/foundation/promise';
5
- import { schemaHasMethod } from '@aztec/foundation/schemas';
6
- import type { FunctionsOf } from '@aztec/foundation/types';
7
-
8
- import {
9
- type EncryptedPayload,
10
- type ExportedPublicKey,
11
- decrypt,
12
- deriveSharedKey,
13
- encrypt,
14
- exportPublicKey,
15
- generateKeyPair,
16
- importPublicKey,
17
- } from '../../crypto.js';
18
- import type { ConnectRequest, WalletInfo, WalletMessage, WalletResponse } from '../../types.js';
19
-
20
- /**
21
- * Internal type representing a wallet method call before encryption.
22
- * @internal
23
- */
24
- type WalletMethodCall = {
25
- /** The wallet method name to invoke */
26
- type: keyof FunctionsOf<Wallet>;
27
- /** Arguments to pass to the wallet method */
28
- args: unknown[];
29
- };
30
-
31
- /**
32
- * A wallet implementation that communicates with browser extension wallets
33
- * using a secure encrypted MessageChannel.
34
- *
35
- * This class establishes a private communication channel with a wallet extension
36
- * using the following security mechanisms:
37
- *
38
- * 1. **MessageChannel**: Creates a private communication channel that is not
39
- * visible to other scripts on the page (unlike window.postMessage).
40
- *
41
- * 2. **ECDH Key Exchange**: Uses Elliptic Curve Diffie-Hellman to derive a
42
- * shared secret between the dApp and wallet without transmitting private keys.
43
- *
44
- * 3. **AES-GCM Encryption**: All messages after channel establishment are
45
- * encrypted using AES-256-GCM, providing both confidentiality and authenticity.
46
- *
47
- * @example
48
- * ```typescript
49
- * // Discovery returns wallet info including the wallet's public key
50
- * const wallets = await ExtensionProvider.discoverExtensions(chainInfo);
51
- * const walletInfo = wallets[0];
52
- *
53
- * // Create a secure connection to the wallet
54
- * const wallet = await ExtensionWallet.create(walletInfo, chainInfo, 'my-dapp');
55
- *
56
- * // All subsequent calls are encrypted
57
- * const accounts = await wallet.getAccounts();
58
- * ```
59
- */
60
- export class ExtensionWallet {
61
- /** Map of pending requests awaiting responses, keyed by message ID */
62
- private inFlight = new Map<string, PromiseWithResolvers<unknown>>();
63
-
64
- /** The MessagePort for private communication with the extension */
65
- private port: MessagePort | null = null;
66
-
67
- /** The derived AES-GCM key for encrypting/decrypting messages */
68
- private sharedKey: CryptoKey | null = null;
69
-
70
- /**
71
- * Private constructor - use {@link ExtensionWallet.create} to instantiate.
72
- * @param chainInfo - The chain information (chainId and version)
73
- * @param appId - Application identifier for the requesting dApp
74
- * @param extensionId - The unique identifier of the target wallet extension
75
- */
76
- private constructor(
77
- private chainInfo: ChainInfo,
78
- private appId: string,
79
- private extensionId: string,
80
- ) {}
81
-
82
- /**
83
- * Creates an ExtensionWallet instance that proxies wallet calls to a browser extension
84
- * over a secure encrypted MessageChannel.
85
- *
86
- * The connection process:
87
- * 1. Generates an ECDH key pair for this session
88
- * 2. Derives a shared AES-256 key using the wallet's public key
89
- * 3. Creates a MessageChannel and transfers one port to the extension
90
- * 4. Returns a Proxy that encrypts all wallet method calls
91
- *
92
- * @param walletInfo - The discovered wallet information, including the wallet's ECDH public key
93
- * @param chainInfo - The chain information (chainId and version) for request context
94
- * @param appId - Application identifier used to identify the requesting dApp to the wallet
95
- * @returns A Promise resolving to a Wallet implementation that encrypts all communication
96
- *
97
- * @throws Error if the secure channel cannot be established
98
- *
99
- * @example
100
- * ```typescript
101
- * const wallet = await ExtensionWallet.create(
102
- * walletInfo,
103
- * { chainId: Fr(31337), version: Fr(0) },
104
- * 'my-defi-app'
105
- * );
106
- * ```
107
- */
108
- static async create(walletInfo: WalletInfo, chainInfo: ChainInfo, appId: string): Promise<Wallet> {
109
- const wallet = new ExtensionWallet(chainInfo, appId, walletInfo.id);
110
-
111
- if (!walletInfo.publicKey) {
112
- throw new Error('Wallet does not support secure channel establishment (missing public key)');
113
- }
114
-
115
- await wallet.establishSecureChannel(walletInfo.publicKey);
116
-
117
- // Create a Proxy that intercepts wallet method calls and forwards them to the extension
118
- return new Proxy(wallet, {
119
- get: (target, prop) => {
120
- if (schemaHasMethod(WalletSchema, prop.toString())) {
121
- return async (...args: unknown[]) => {
122
- const result = await target.postMessage({
123
- type: prop.toString() as keyof FunctionsOf<Wallet>,
124
- args,
125
- });
126
- return WalletSchema[prop.toString() as keyof typeof WalletSchema].returnType().parseAsync(result);
127
- };
128
- } else {
129
- return target[prop as keyof ExtensionWallet];
130
- }
131
- },
132
- }) as unknown as Wallet;
133
- }
134
-
135
- /**
136
- * Establishes a secure MessageChannel with ECDH key exchange.
137
- *
138
- * This method performs the cryptographic handshake:
139
- * 1. Generates a new ECDH P-256 key pair for this session
140
- * 2. Imports the wallet's public key and derives a shared secret
141
- * 3. Creates a MessageChannel for private communication
142
- * 4. Sends a connection request with our public key via window.postMessage
143
- * (this is the only public message - subsequent communication uses the private channel)
144
- *
145
- * @param walletExportedPublicKey - The wallet's ECDH public key in JWK format
146
- */
147
- private async establishSecureChannel(walletExportedPublicKey: ExportedPublicKey): Promise<void> {
148
- const keyPair = await generateKeyPair();
149
- const exportedPublicKey = await exportPublicKey(keyPair.publicKey);
150
-
151
- const walletPublicKey = await importPublicKey(walletExportedPublicKey);
152
- this.sharedKey = await deriveSharedKey(keyPair.privateKey, walletPublicKey);
153
-
154
- const channel = new MessageChannel();
155
- this.port = channel.port1;
156
-
157
- this.port.onmessage = async (event: MessageEvent<EncryptedPayload>) => {
158
- await this.handleEncryptedResponse(event.data);
159
- };
160
-
161
- this.port.start();
162
-
163
- // Send connection request with our public key and transfer port2 to content script
164
- // This is the only public postMessage - it contains our public key (safe to expose)
165
- // and transfers the MessagePort for subsequent private communication
166
- const connectRequest: ConnectRequest = {
167
- type: 'aztec-wallet-connect',
168
- walletId: this.extensionId,
169
- appId: this.appId,
170
- publicKey: exportedPublicKey,
171
- };
172
-
173
- window.postMessage(jsonStringify(connectRequest), '*', [channel.port2]);
174
- }
175
-
176
- /**
177
- * Handles an encrypted response received from the wallet extension.
178
- *
179
- * Decrypts the response using the shared AES key and resolves or rejects
180
- * the corresponding pending promise based on the response content.
181
- *
182
- * @param encrypted - The encrypted response from the wallet
183
- */
184
- private async handleEncryptedResponse(encrypted: EncryptedPayload): Promise<void> {
185
- if (!this.sharedKey) {
186
- return;
187
- }
188
-
189
- try {
190
- const response = await decrypt<WalletResponse>(this.sharedKey, encrypted);
191
-
192
- const { messageId, result, error, walletId: responseWalletId } = response;
193
-
194
- if (!messageId || !responseWalletId) {
195
- return;
196
- }
197
-
198
- if (this.extensionId !== responseWalletId) {
199
- return;
200
- }
201
-
202
- if (!this.inFlight.has(messageId)) {
203
- return;
204
- }
205
-
206
- const { resolve, reject } = this.inFlight.get(messageId)!;
207
-
208
- if (error) {
209
- reject(new Error(jsonStringify(error)));
210
- } else {
211
- resolve(result);
212
- }
213
- this.inFlight.delete(messageId);
214
- // eslint-disable-next-line no-empty
215
- } catch {}
216
- }
217
-
218
- /**
219
- * Sends an encrypted wallet method call over the secure MessageChannel.
220
- *
221
- * The message is encrypted using AES-256-GCM with the shared key derived
222
- * during channel establishment. A unique message ID is generated to correlate
223
- * the response.
224
- *
225
- * @param call - The wallet method call containing method name and arguments
226
- * @returns A Promise that resolves with the decrypted result from the wallet
227
- *
228
- * @throws Error if the secure channel has not been established
229
- */
230
- private async postMessage(call: WalletMethodCall): Promise<unknown> {
231
- if (!this.port || !this.sharedKey) {
232
- throw new Error('Secure channel not established');
233
- }
234
-
235
- const messageId = globalThis.crypto.randomUUID();
236
- const message: WalletMessage = {
237
- type: call.type,
238
- args: call.args,
239
- messageId,
240
- chainInfo: this.chainInfo,
241
- appId: this.appId,
242
- walletId: this.extensionId,
243
- };
244
-
245
- // Encrypt the message and send over the private MessageChannel
246
- const encrypted = await encrypt(this.sharedKey, message);
247
- this.port.postMessage(encrypted);
248
-
249
- const { promise, resolve, reject } = promiseWithResolvers<unknown>();
250
- this.inFlight.set(messageId, { promise, resolve, reject });
251
- return promise;
252
- }
253
-
254
- /**
255
- * Closes the secure channel and cleans up resources.
256
- *
257
- * After calling this method, the wallet instance can no longer be used.
258
- * Any pending requests will not receive responses.
259
- *
260
- * @example
261
- * ```typescript
262
- * const wallet = await ExtensionWallet.create(walletInfo, chainInfo, 'my-app');
263
- * // ... use wallet ...
264
- * wallet.close(); // Clean up when done
265
- * ```
266
- */
267
- close(): void {
268
- if (this.port) {
269
- this.port.close();
270
- this.port = null;
271
- }
272
- this.sharedKey = null;
273
- this.inFlight.clear();
274
- }
275
- }