@aztec/wallet-sdk 0.0.1-commit.f295ac2 → 0.0.1-commit.f8ca9b2f3

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 (75) hide show
  1. package/README.md +217 -294
  2. package/dest/base-wallet/base_wallet.d.ts +38 -8
  3. package/dest/base-wallet/base_wallet.d.ts.map +1 -1
  4. package/dest/base-wallet/base_wallet.js +81 -18
  5. package/dest/base-wallet/index.d.ts +2 -1
  6. package/dest/base-wallet/index.d.ts.map +1 -1
  7. package/dest/base-wallet/index.js +1 -0
  8. package/dest/base-wallet/utils.d.ts +48 -0
  9. package/dest/base-wallet/utils.d.ts.map +1 -0
  10. package/dest/base-wallet/utils.js +128 -0
  11. package/dest/crypto.d.ts +59 -50
  12. package/dest/crypto.d.ts.map +1 -1
  13. package/dest/crypto.js +202 -108
  14. package/dest/emoji_alphabet.d.ts +35 -0
  15. package/dest/emoji_alphabet.d.ts.map +1 -0
  16. package/dest/emoji_alphabet.js +299 -0
  17. package/dest/extension/handlers/background_connection_handler.d.ts +158 -0
  18. package/dest/extension/handlers/background_connection_handler.d.ts.map +1 -0
  19. package/dest/extension/handlers/background_connection_handler.js +258 -0
  20. package/dest/extension/handlers/content_script_connection_handler.d.ts +56 -0
  21. package/dest/extension/handlers/content_script_connection_handler.d.ts.map +1 -0
  22. package/dest/extension/handlers/content_script_connection_handler.js +174 -0
  23. package/dest/extension/handlers/index.d.ts +12 -0
  24. package/dest/extension/handlers/index.d.ts.map +1 -0
  25. package/dest/extension/handlers/index.js +10 -0
  26. package/dest/extension/handlers/internal_message_types.d.ts +63 -0
  27. package/dest/extension/handlers/internal_message_types.d.ts.map +1 -0
  28. package/dest/extension/handlers/internal_message_types.js +22 -0
  29. package/dest/extension/provider/extension_provider.d.ts +107 -0
  30. package/dest/extension/provider/extension_provider.d.ts.map +1 -0
  31. package/dest/extension/provider/extension_provider.js +160 -0
  32. package/dest/extension/provider/extension_wallet.d.ts +131 -0
  33. package/dest/extension/provider/extension_wallet.d.ts.map +1 -0
  34. package/dest/{providers/extension → extension/provider}/extension_wallet.js +48 -95
  35. package/dest/extension/provider/index.d.ts +3 -0
  36. package/dest/extension/provider/index.d.ts.map +1 -0
  37. package/dest/{providers/extension → extension/provider}/index.js +0 -2
  38. package/dest/manager/index.d.ts +2 -8
  39. package/dest/manager/index.d.ts.map +1 -1
  40. package/dest/manager/index.js +0 -6
  41. package/dest/manager/types.d.ts +88 -6
  42. package/dest/manager/types.d.ts.map +1 -1
  43. package/dest/manager/types.js +17 -1
  44. package/dest/manager/wallet_manager.d.ts +50 -7
  45. package/dest/manager/wallet_manager.d.ts.map +1 -1
  46. package/dest/manager/wallet_manager.js +174 -44
  47. package/dest/types.d.ts +43 -12
  48. package/dest/types.d.ts.map +1 -1
  49. package/dest/types.js +3 -2
  50. package/package.json +17 -10
  51. package/src/base-wallet/base_wallet.ts +122 -35
  52. package/src/base-wallet/index.ts +1 -0
  53. package/src/base-wallet/utils.ts +229 -0
  54. package/src/crypto.ts +237 -113
  55. package/src/emoji_alphabet.ts +317 -0
  56. package/src/extension/handlers/background_connection_handler.ts +423 -0
  57. package/src/extension/handlers/content_script_connection_handler.ts +246 -0
  58. package/src/extension/handlers/index.ts +25 -0
  59. package/src/extension/handlers/internal_message_types.ts +69 -0
  60. package/src/extension/provider/extension_provider.ts +233 -0
  61. package/src/{providers/extension → extension/provider}/extension_wallet.ts +52 -110
  62. package/src/extension/provider/index.ts +7 -0
  63. package/src/manager/index.ts +2 -10
  64. package/src/manager/types.ts +91 -5
  65. package/src/manager/wallet_manager.ts +192 -46
  66. package/src/types.ts +44 -10
  67. package/dest/providers/extension/extension_provider.d.ts +0 -63
  68. package/dest/providers/extension/extension_provider.d.ts.map +0 -1
  69. package/dest/providers/extension/extension_provider.js +0 -124
  70. package/dest/providers/extension/extension_wallet.d.ts +0 -155
  71. package/dest/providers/extension/extension_wallet.d.ts.map +0 -1
  72. package/dest/providers/extension/index.d.ts +0 -6
  73. package/dest/providers/extension/index.d.ts.map +0 -1
  74. package/src/providers/extension/extension_provider.ts +0 -167
  75. package/src/providers/extension/index.ts +0 -5
package/dest/crypto.d.ts CHANGED
@@ -36,11 +36,23 @@ export interface SecureKeyPair {
36
36
  /** Private key - keep secret, used for key derivation */
37
37
  privateKey: CryptoKey;
38
38
  }
39
+ /**
40
+ * Session keys derived from ECDH key exchange.
41
+ *
42
+ * Contains both the encryption key and the verification hash (verificationHash)
43
+ * computed from a separate HMAC key.
44
+ */
45
+ export interface SessionKeys {
46
+ /** AES-256-GCM key for message encryption/decryption */
47
+ encryptionKey: CryptoKey;
48
+ /** Hex-encoded verificationHash for verification */
49
+ verificationHash: string;
50
+ }
39
51
  /**
40
52
  * Generates an ECDH P-256 key pair for key exchange.
41
53
  *
42
- * The generated key pair can be used to derive a shared secret with another
43
- * party's public key using {@link deriveSharedKey}.
54
+ * The generated key pair can be used to derive session keys with another
55
+ * party's public key using {@link deriveSessionKeys}.
44
56
  *
45
57
  * @returns A new ECDH key pair
46
58
  *
@@ -72,65 +84,71 @@ export declare function exportPublicKey(publicKey: CryptoKey): Promise<ExportedP
72
84
  /**
73
85
  * Imports a public key from JWK format.
74
86
  *
75
- * Used to import the other party's public key for deriving a shared secret.
87
+ * Used to import the other party's public key for deriving session keys.
76
88
  *
77
89
  * @param exported - The public key in JWK format
78
- * @returns A CryptoKey that can be used with {@link deriveSharedKey}
90
+ * @returns A CryptoKey that can be used with {@link deriveSessionKeys}
79
91
  *
80
92
  * @example
81
93
  * ```typescript
82
- * // Receive exported public key from other party
83
- * const theirPublicKey = await importPublicKey(receivedPublicKey);
84
- * const sharedKey = await deriveSharedKey(myPrivateKey, theirPublicKey);
94
+ * // App side: receive wallet's public key and derive session
95
+ * const walletPublicKey = await importPublicKey(receivedWalletKey);
96
+ * const session = await deriveSessionKeys(appKeyPair, walletPublicKey, true);
85
97
  * ```
86
98
  */
87
99
  export declare function importPublicKey(exported: ExportedPublicKey): Promise<CryptoKey>;
88
100
  /**
89
- * Derives a shared AES-256-GCM key from ECDH key exchange.
101
+ * Derives session keys from ECDH key exchange using HKDF.
102
+ *
103
+ * This is the main key derivation function that produces:
104
+ * 1. An AES-256-GCM encryption key (first 256 bits)
105
+ * 2. An HMAC key for verificationHash computation (second 256 bits)
106
+ * 3. A verificationHash computed as HMAC(hmacKey, "aztec-wallet-verification-verificationHash")
90
107
  *
91
- * Both parties will derive the same shared key when using their own private key
92
- * and the other party's public key. This is the core of ECDH key agreement.
108
+ * The keys are derived using a single HKDF call that produces 512 bits,
109
+ * then split into the two keys.
93
110
  *
94
- * @param privateKey - Your ECDH private key
95
- * @param publicKey - The other party's ECDH public key
96
- * @returns An AES-256-GCM key for encryption/decryption
111
+ * @param ownKeyPair - The caller's ECDH key pair (private for ECDH, public for salt)
112
+ * @param peerPublicKey - The peer's ECDH public key (for ECDH and salt)
113
+ * @param isApp - true if caller is the app, false if caller is the wallet
114
+ * @returns Session keys containing encryption key and verificationHash
97
115
  *
98
116
  * @example
99
117
  * ```typescript
100
- * // Both parties derive the same key
101
- * const sharedKeyA = await deriveSharedKey(privateKeyA, publicKeyB);
102
- * const sharedKeyB = await deriveSharedKey(privateKeyB, publicKeyA);
103
- * // sharedKeyA and sharedKeyB are equivalent
118
+ * // App side
119
+ * const sessionA = await deriveSessionKeys(appKeyPair, walletPublicKey, true);
120
+ * // Wallet side
121
+ * const sessionB = await deriveSessionKeys(walletKeyPair, appPublicKey, false);
122
+ * // sessionA.verificationHash === sessionB.verificationHash
104
123
  * ```
105
124
  */
106
- export declare function deriveSharedKey(privateKey: CryptoKey, publicKey: CryptoKey): Promise<CryptoKey>;
125
+ export declare function deriveSessionKeys(ownKeyPair: SecureKeyPair, peerPublicKey: CryptoKey, isApp: boolean): Promise<SessionKeys>;
107
126
  /**
108
127
  * Encrypts data using AES-256-GCM.
109
128
  *
110
- * The data is JSON serialized before encryption. A random 12-byte IV is
111
- * generated for each encryption operation.
129
+ * A random 12-byte IV is generated for each encryption operation.
112
130
  *
113
131
  * AES-GCM provides both confidentiality and authenticity - any tampering
114
132
  * with the ciphertext will cause decryption to fail.
115
133
  *
116
- * @param key - The AES-GCM key (from {@link deriveSharedKey})
117
- * @param data - The data to encrypt (will be JSON serialized)
134
+ * @param key - The AES-GCM key (from {@link deriveSessionKeys})
135
+ * @param data - The string data to encrypt (caller is responsible for serialization)
118
136
  * @returns The encrypted payload with IV and ciphertext
119
137
  *
120
138
  * @example
121
139
  * ```typescript
122
- * const encrypted = await encrypt(sharedKey, { action: 'transfer', amount: 100 });
140
+ * const encrypted = await encrypt(session.encryptionKey, JSON.stringify({ action: 'transfer', amount: 100 }));
123
141
  * // encrypted.iv and encrypted.ciphertext are base64 strings
124
142
  * ```
125
143
  */
126
- export declare function encrypt(key: CryptoKey, data: unknown): Promise<EncryptedPayload>;
144
+ export declare function encrypt(key: CryptoKey, data: string): Promise<EncryptedPayload>;
127
145
  /**
128
146
  * Decrypts data using AES-256-GCM.
129
147
  *
130
148
  * The decrypted data is JSON parsed before returning.
131
149
  *
132
150
  * @typeParam T - The expected type of the decrypted data
133
- * @param key - The AES-GCM key (from {@link deriveSharedKey})
151
+ * @param key - The AES-GCM key (from {@link deriveSessionKeys})
134
152
  * @param payload - The encrypted payload from {@link encrypt}
135
153
  * @returns The decrypted and parsed data
136
154
  *
@@ -138,46 +156,37 @@ export declare function encrypt(key: CryptoKey, data: unknown): Promise<Encrypte
138
156
  *
139
157
  * @example
140
158
  * ```typescript
141
- * const decrypted = await decrypt<{ action: string; amount: number }>(sharedKey, encrypted);
159
+ * const decrypted = await decrypt<{ action: string; amount: number }>(session.encryptionKey, encrypted);
142
160
  * console.log(decrypted.action); // 'transfer'
143
161
  * ```
144
162
  */
145
163
  export declare function decrypt<T = unknown>(key: CryptoKey, payload: EncryptedPayload): Promise<T>;
146
164
  /**
147
- * Hashes a shared AES key to a hex string for verification.
148
- *
149
- * This extracts the raw key material and hashes it with SHA-256,
150
- * returning the first 16 bytes as a hex string.
151
- *
152
- * @param sharedKey - The AES-GCM shared key (must be extractable)
153
- * @returns A hex string representation of the hash
154
- *
155
- * @example
156
- * ```typescript
157
- * const hash = await hashSharedSecret(sharedKey);
158
- * const emoji = hashToEmoji(hash);
159
- * ```
165
+ * Default grid size for emoji verification display.
166
+ * 3x3 grid = 9 emojis = 72 bits of security.
160
167
  */
161
- export declare function hashSharedSecret(sharedKey: CryptoKey): Promise<string>;
168
+ export declare const DEFAULT_EMOJI_GRID_SIZE = 9;
162
169
  /**
163
170
  * Converts a hex hash to an emoji sequence for visual verification.
164
171
  *
165
- * This is used for anti-MITM verification - both the dApp and wallet
166
- * independently compute the same emoji sequence from the shared secret.
172
+ * This is used for verification - both the dApp and wallet
173
+ * independently compute the same emoji sequence from the derived keys.
167
174
  * Users can visually compare the sequences to detect interception.
168
175
  *
169
- * Similar to SAS (Short Authentication String) in ZRTP/Signal.
176
+ * With a 256-emoji alphabet and 9 emojis (3x3 grid), this provides
177
+ * 72 bits of security (9 * 8 = 72 bits), making brute-force attacks
178
+ * computationally infeasible.
170
179
  *
171
- * @param hash - Hex string from {@link hashSharedSecret}
172
- * @param length - Number of emojis to generate (default: 4)
180
+ * @param hash - Hex string from verification hash (64 chars = 32 bytes)
181
+ * @param count - Number of emojis to generate (default: 9 for 3x3 grid)
173
182
  * @returns A string of emojis representing the hash
174
183
  *
175
184
  * @example
176
185
  * ```typescript
177
- * const hash = await hashSharedSecret(sharedKey);
178
- * const emoji = hashToEmoji(hash); // e.g., "🔵🦋🎯🐼"
179
- * // Display to user for verification
186
+ * const session = await deriveSessionKeys(...);
187
+ * const emoji = hashToEmoji(session.verificationHash); // e.g., "🔵🦋🎯🐼🌟🎲🦊🐸💎"
188
+ * // Display as 3x3 grid to user for verification
180
189
  * ```
181
190
  */
182
- export declare function hashToEmoji(hash: string, length?: number): string;
183
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3J5cHRvLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY3J5cHRvLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQW1DQTs7OztHQUlHO0FBQ0gsTUFBTSxXQUFXLGlCQUFpQjtJQUNoQyxnREFBZ0Q7SUFDaEQsR0FBRyxFQUFFLE1BQU0sQ0FBQztJQUNaLGtDQUFrQztJQUNsQyxHQUFHLEVBQUUsTUFBTSxDQUFDO0lBQ1osdUNBQXVDO0lBQ3ZDLENBQUMsRUFBRSxNQUFNLENBQUM7SUFDVix1Q0FBdUM7SUFDdkMsQ0FBQyxFQUFFLE1BQU0sQ0FBQztDQUNYO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sV0FBVyxnQkFBZ0I7SUFDL0IsdURBQXVEO0lBQ3ZELEVBQUUsRUFBRSxNQUFNLENBQUM7SUFDWCxrQ0FBa0M7SUFDbEMsVUFBVSxFQUFFLE1BQU0sQ0FBQztDQUNwQjtBQUVEOzs7OztHQUtHO0FBQ0gsTUFBTSxXQUFXLGFBQWE7SUFDNUIsaUNBQWlDO0lBQ2pDLFNBQVMsRUFBRSxTQUFTLENBQUM7SUFDckIseURBQXlEO0lBQ3pELFVBQVUsRUFBRSxTQUFTLENBQUM7Q0FDdkI7QUFFRDs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILHdCQUFzQixlQUFlLElBQUksT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQWE5RDtBQUVEOzs7Ozs7Ozs7Ozs7Ozs7R0FlRztBQUNILHdCQUFzQixlQUFlLENBQUMsU0FBUyxFQUFFLFNBQVMsR0FBRyxPQUFPLENBQUMsaUJBQWlCLENBQUMsQ0FRdEY7QUFFRDs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILHdCQUFnQixlQUFlLENBQUMsUUFBUSxFQUFFLGlCQUFpQixHQUFHLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FnQi9FO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJHO0FBQ0gsd0JBQWdCLGVBQWUsQ0FBQyxVQUFVLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxTQUFTLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQWMvRjtBQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FrQkc7QUFDSCx3QkFBc0IsT0FBTyxDQUFDLEdBQUcsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLE9BQU8sR0FBRyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsQ0FVdEY7QUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FpQkc7QUFDSCx3QkFBc0IsT0FBTyxDQUFDLENBQUMsR0FBRyxPQUFPLEVBQUUsR0FBRyxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQVFoRztBQW9FRDs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILHdCQUFzQixnQkFBZ0IsQ0FBQyxTQUFTLEVBQUUsU0FBUyxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FPNUU7QUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRztBQUNILHdCQUFnQixXQUFXLENBQUMsSUFBSSxFQUFFLE1BQU0sRUFBRSxNQUFNLEdBQUUsTUFBVSxHQUFHLE1BQU0sQ0FNcEUifQ==
191
+ export declare function hashToEmoji(hash: string, count?: number): string;
192
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3J5cHRvLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY3J5cHRvLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQThEQTs7OztHQUlHO0FBQ0gsTUFBTSxXQUFXLGlCQUFpQjtJQUNoQyxnREFBZ0Q7SUFDaEQsR0FBRyxFQUFFLE1BQU0sQ0FBQztJQUNaLGtDQUFrQztJQUNsQyxHQUFHLEVBQUUsTUFBTSxDQUFDO0lBQ1osdUNBQXVDO0lBQ3ZDLENBQUMsRUFBRSxNQUFNLENBQUM7SUFDVix1Q0FBdUM7SUFDdkMsQ0FBQyxFQUFFLE1BQU0sQ0FBQztDQUNYO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sV0FBVyxnQkFBZ0I7SUFDL0IsdURBQXVEO0lBQ3ZELEVBQUUsRUFBRSxNQUFNLENBQUM7SUFDWCxrQ0FBa0M7SUFDbEMsVUFBVSxFQUFFLE1BQU0sQ0FBQztDQUNwQjtBQUVEOzs7OztHQUtHO0FBQ0gsTUFBTSxXQUFXLGFBQWE7SUFDNUIsaUNBQWlDO0lBQ2pDLFNBQVMsRUFBRSxTQUFTLENBQUM7SUFDckIseURBQXlEO0lBQ3pELFVBQVUsRUFBRSxTQUFTLENBQUM7Q0FDdkI7QUFFRDs7Ozs7R0FLRztBQUNILE1BQU0sV0FBVyxXQUFXO0lBQzFCLHdEQUF3RDtJQUN4RCxhQUFhLEVBQUUsU0FBUyxDQUFDO0lBQ3pCLG9EQUFvRDtJQUNwRCxnQkFBZ0IsRUFBRSxNQUFNLENBQUM7Q0FDMUI7QUFTRDs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILHdCQUFzQixlQUFlLElBQUksT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQWE5RDtBQUVEOzs7Ozs7Ozs7Ozs7Ozs7R0FlRztBQUNILHdCQUFzQixlQUFlLENBQUMsU0FBUyxFQUFFLFNBQVMsR0FBRyxPQUFPLENBQUMsaUJBQWlCLENBQUMsQ0FRdEY7QUFFRDs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILHdCQUFnQixlQUFlLENBQUMsUUFBUSxFQUFFLGlCQUFpQixHQUFHLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FnQi9FO0FBc0REOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F3Qkc7QUFDSCx3QkFBc0IsaUJBQWlCLENBQ3JDLFVBQVUsRUFBRSxhQUFhLEVBQ3pCLGFBQWEsRUFBRSxTQUFTLEVBQ3hCLEtBQUssRUFBRSxPQUFPLEdBQ2IsT0FBTyxDQUFDLFdBQVcsQ0FBQyxDQXdEdEI7QUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FpQkc7QUFDSCx3QkFBc0IsT0FBTyxDQUFDLEdBQUcsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLE1BQU0sR0FBRyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsQ0FVckY7QUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FpQkc7QUFDSCx3QkFBc0IsT0FBTyxDQUFDLENBQUMsR0FBRyxPQUFPLEVBQUUsR0FBRyxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQVFoRztBQXdERDs7O0dBR0c7QUFDSCxlQUFPLE1BQU0sdUJBQXVCLElBQUksQ0FBQztBQUV6Qzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBcUJHO0FBQ0gsd0JBQWdCLFdBQVcsQ0FBQyxJQUFJLEVBQUUsTUFBTSxFQUFFLEtBQUssR0FBRSxNQUFnQyxHQUFHLE1BQU0sQ0FPekYifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAmCA;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,CAAC,EAAE,MAAM,CAAC;IACV,uCAAuC;IACvC,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,SAAS,EAAE,SAAS,CAAC;IACrB,yDAAyD;IACzD,UAAU,EAAE,SAAS,CAAC;CACvB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,aAAa,CAAC,CAa9D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAQtF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC,CAgB/E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAc/F;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAUtF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,CAAC,CAQhG;AAoED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAO5E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAU,GAAG,MAAM,CAMpE"}
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AA8DA;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,CAAC,EAAE,MAAM,CAAC;IACV,uCAAuC;IACvC,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,SAAS,EAAE,SAAS,CAAC;IACrB,yDAAyD;IACzD,UAAU,EAAE,SAAS,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,aAAa,EAAE,SAAS,CAAC;IACzB,oDAAoD;IACpD,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AASD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,aAAa,CAAC,CAa9D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAQtF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC,CAgB/E;AAsDD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,aAAa,EACzB,aAAa,EAAE,SAAS,EACxB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,WAAW,CAAC,CAwDtB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAUrF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,CAAC,CAQhG;AAwDD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAAgC,GAAG,MAAM,CAOzF"}
package/dest/crypto.js CHANGED
@@ -4,38 +4,69 @@
4
4
  * This module provides ECDH key exchange and AES-GCM encryption primitives
5
5
  * for establishing secure communication channels between dApps and wallet extensions.
6
6
  *
7
- * The crypto flow:
7
+ * ## Security Model
8
+ *
9
+ * The crypto flow uses HKDF for key derivation with domain separation:
10
+ *
8
11
  * 1. Both parties generate ECDH key pairs using {@link generateKeyPair}
9
12
  * 2. Public keys are exchanged (exported via {@link exportPublicKey}, imported via {@link importPublicKey})
10
- * 3. Both parties derive the same shared secret using {@link deriveSharedKey}
11
- * 4. Messages are encrypted/decrypted using {@link encrypt} and {@link decrypt}
13
+ * 3. Both parties derive keys using {@link deriveSessionKeys}:
14
+ * - ECDH produces raw shared secret
15
+ * - HKDF expands the secret into 512 bits using concatenated public keys as salt
16
+ * - The 512 bits are split: first 256 bits for AES-GCM, second 256 bits for HMAC
17
+ * 4. Fingerprint is computed as HMAC(HMAC_KEY, "aztec-wallet-verification-verificationHash")
18
+ * 5. Messages are encrypted/decrypted using {@link encrypt} and {@link decrypt}
19
+ *
20
+ * This design ensures:
21
+ * - The encryption key is never exposed (verificationHash uses separate HMAC key)
22
+ * - Public keys are bound to the derived keys via HKDF salt
23
+ * - Single HKDF derivation with domain-separated output splitting
24
+ *
25
+ * ## Curve Choice
26
+ *
27
+ * We use P-256 (secp256r1) because it's the only ECDH curve with broad Web Crypto API
28
+ * support across all browsers. X25519 would be preferable for its simplicity and
29
+ * resistance to implementation errors, but it lacks universal browser support.
12
30
  *
13
31
  * @example
14
32
  * ```typescript
15
- * // Party A
33
+ * // Party A (dApp)
16
34
  * const keyPairA = await generateKeyPair();
17
35
  * const publicKeyA = await exportPublicKey(keyPairA.publicKey);
18
36
  *
19
- * // Party B
37
+ * // Party B (wallet)
20
38
  * const keyPairB = await generateKeyPair();
21
39
  * const publicKeyB = await exportPublicKey(keyPairB.publicKey);
22
40
  *
23
- * // Exchange public keys, then derive shared secret
41
+ * // Exchange public keys, then derive session keys
42
+ * // App side: isApp = true
24
43
  * const importedB = await importPublicKey(publicKeyB);
25
- * const sharedKeyA = await deriveSharedKey(keyPairA.privateKey, importedB);
44
+ * const sessionA = await deriveSessionKeys(keyPairA, importedB, true);
45
+ *
46
+ * // Wallet side: isApp = false
47
+ * const importedA = await importPublicKey(publicKeyA);
48
+ * const sessionB = await deriveSessionKeys(keyPairB, importedA, false);
49
+ *
50
+ * // Both parties compute the same verificationHash for verification
51
+ * const verificationHashA = sessionA.verificationHash;
52
+ * const emojiA = hashToEmoji(verificationHashA);
26
53
  *
27
54
  * // Encrypt and decrypt
28
- * const encrypted = await encrypt(sharedKeyA, { message: 'hello' });
29
- * const decrypted = await decrypt(sharedKeyB, encrypted);
55
+ * const encrypted = await encrypt(sessionA.encryptionKey, JSON.stringify({ message: 'hello' }));
56
+ * const decrypted = await decrypt(sessionB.encryptionKey, encrypted);
30
57
  * ```
31
58
  *
32
59
  * @packageDocumentation
33
- */ import { jsonStringify } from '@aztec/foundation/json-rpc';
60
+ */ import { EMOJI_ALPHABET, EMOJI_ALPHABET_SIZE } from './emoji_alphabet.js';
61
+ /** P-256 coordinate size in bytes */ const P256_COORDINATE_SIZE = 32;
62
+ // HKDF info string for key derivation
63
+ const HKDF_INFO = new TextEncoder().encode('Aztec Wallet DAPP Key derivation');
64
+ const FINGERPRINT_DATA = new TextEncoder().encode('aztec-wallet-verification-verificationHash');
34
65
  /**
35
66
  * Generates an ECDH P-256 key pair for key exchange.
36
67
  *
37
- * The generated key pair can be used to derive a shared secret with another
38
- * party's public key using {@link deriveSharedKey}.
68
+ * The generated key pair can be used to derive session keys with another
69
+ * party's public key using {@link deriveSessionKeys}.
39
70
  *
40
71
  * @returns A new ECDH key pair
41
72
  *
@@ -50,7 +81,7 @@
50
81
  name: 'ECDH',
51
82
  namedCurve: 'P-256'
52
83
  }, true, [
53
- 'deriveKey'
84
+ 'deriveBits'
54
85
  ]);
55
86
  return {
56
87
  publicKey: keyPair.publicKey,
@@ -84,16 +115,16 @@
84
115
  /**
85
116
  * Imports a public key from JWK format.
86
117
  *
87
- * Used to import the other party's public key for deriving a shared secret.
118
+ * Used to import the other party's public key for deriving session keys.
88
119
  *
89
120
  * @param exported - The public key in JWK format
90
- * @returns A CryptoKey that can be used with {@link deriveSharedKey}
121
+ * @returns A CryptoKey that can be used with {@link deriveSessionKeys}
91
122
  *
92
123
  * @example
93
124
  * ```typescript
94
- * // Receive exported public key from other party
95
- * const theirPublicKey = await importPublicKey(receivedPublicKey);
96
- * const sharedKey = await deriveSharedKey(myPrivateKey, theirPublicKey);
125
+ * // App side: receive wallet's public key and derive session
126
+ * const walletPublicKey = await importPublicKey(receivedWalletKey);
127
+ * const session = await deriveSessionKeys(appKeyPair, walletPublicKey, true);
97
128
  * ```
98
129
  */ export function importPublicKey(exported) {
99
130
  return crypto.subtle.importKey('jwk', {
@@ -104,58 +135,150 @@
104
135
  }, {
105
136
  name: 'ECDH',
106
137
  namedCurve: 'P-256'
107
- }, false, []);
138
+ }, true, []);
139
+ }
140
+ /**
141
+ * Decodes a base64url-encoded coordinate to fixed-size bytes.
142
+ *
143
+ * For P-256, coordinates are always 32 bytes. This function ensures
144
+ * consistent serialization regardless of leading zeros.
145
+ *
146
+ * @param base64url - Base64url-encoded coordinate
147
+ * @param size - Expected size in bytes (32 for P-256)
148
+ * @returns Fixed-size Uint8Array, left-padded with zeros if needed
149
+ */ function decodeCoordinateFixedSize(base64url, size) {
150
+ const decoded = base64UrlToBytes(base64url);
151
+ if (decoded.length === size) {
152
+ return decoded;
153
+ }
154
+ if (decoded.length > size) {
155
+ throw new Error(`Invalid P-256 coordinate: expected ${size} bytes, got ${decoded.length}`);
156
+ }
157
+ // Left-pad with zeros
158
+ const padded = new Uint8Array(size);
159
+ padded.set(decoded, size - decoded.length);
160
+ return padded;
161
+ }
162
+ /**
163
+ * Creates HKDF salt from public keys with fixed ordering by party role.
164
+ *
165
+ * The app's public key always comes first, followed by the wallet's public key.
166
+ * This ensures both parties produce the same salt.
167
+ *
168
+ * @param appKey - The app's public key in exported format
169
+ * @param walletKey - The wallet's public key in exported format
170
+ * @returns Concatenated bytes: app_x || app_y || wallet_x || wallet_y (128 bytes for P-256)
171
+ */ function createSaltFromPublicKeys(appKey, walletKey) {
172
+ // Fixed ordering: app first, then wallet
173
+ // Each coordinate is fixed at 32 bytes for P-256
174
+ const appX = decodeCoordinateFixedSize(appKey.x, P256_COORDINATE_SIZE);
175
+ const appY = decodeCoordinateFixedSize(appKey.y, P256_COORDINATE_SIZE);
176
+ const walletX = decodeCoordinateFixedSize(walletKey.x, P256_COORDINATE_SIZE);
177
+ const walletY = decodeCoordinateFixedSize(walletKey.y, P256_COORDINATE_SIZE);
178
+ // Total: 4 * 32 = 128 bytes
179
+ const salt = new Uint8Array(4 * P256_COORDINATE_SIZE);
180
+ salt.set(appX, 0);
181
+ salt.set(appY, P256_COORDINATE_SIZE);
182
+ salt.set(walletX, 2 * P256_COORDINATE_SIZE);
183
+ salt.set(walletY, 3 * P256_COORDINATE_SIZE);
184
+ return salt.buffer;
108
185
  }
109
186
  /**
110
- * Derives a shared AES-256-GCM key from ECDH key exchange.
187
+ * Derives session keys from ECDH key exchange using HKDF.
188
+ *
189
+ * This is the main key derivation function that produces:
190
+ * 1. An AES-256-GCM encryption key (first 256 bits)
191
+ * 2. An HMAC key for verificationHash computation (second 256 bits)
192
+ * 3. A verificationHash computed as HMAC(hmacKey, "aztec-wallet-verification-verificationHash")
111
193
  *
112
- * Both parties will derive the same shared key when using their own private key
113
- * and the other party's public key. This is the core of ECDH key agreement.
194
+ * The keys are derived using a single HKDF call that produces 512 bits,
195
+ * then split into the two keys.
114
196
  *
115
- * @param privateKey - Your ECDH private key
116
- * @param publicKey - The other party's ECDH public key
117
- * @returns An AES-256-GCM key for encryption/decryption
197
+ * @param ownKeyPair - The caller's ECDH key pair (private for ECDH, public for salt)
198
+ * @param peerPublicKey - The peer's ECDH public key (for ECDH and salt)
199
+ * @param isApp - true if caller is the app, false if caller is the wallet
200
+ * @returns Session keys containing encryption key and verificationHash
118
201
  *
119
202
  * @example
120
203
  * ```typescript
121
- * // Both parties derive the same key
122
- * const sharedKeyA = await deriveSharedKey(privateKeyA, publicKeyB);
123
- * const sharedKeyB = await deriveSharedKey(privateKeyB, publicKeyA);
124
- * // sharedKeyA and sharedKeyB are equivalent
204
+ * // App side
205
+ * const sessionA = await deriveSessionKeys(appKeyPair, walletPublicKey, true);
206
+ * // Wallet side
207
+ * const sessionB = await deriveSessionKeys(walletKeyPair, appPublicKey, false);
208
+ * // sessionA.verificationHash === sessionB.verificationHash
125
209
  * ```
126
- */ export function deriveSharedKey(privateKey, publicKey) {
127
- return crypto.subtle.deriveKey({
210
+ */ export async function deriveSessionKeys(ownKeyPair, peerPublicKey, isApp) {
211
+ // Step 1: ECDH to get raw shared secret
212
+ const sharedSecretBits = await crypto.subtle.deriveBits({
128
213
  name: 'ECDH',
129
- public: publicKey
130
- }, privateKey, {
214
+ public: peerPublicKey
215
+ }, ownKeyPair.privateKey, 256);
216
+ // Step 2: Import shared secret as HKDF key material
217
+ const hkdfKey = await crypto.subtle.importKey('raw', sharedSecretBits, {
218
+ name: 'HKDF'
219
+ }, false, [
220
+ 'deriveBits'
221
+ ]);
222
+ // Step 3: Export public keys and create salt (app first, wallet second)
223
+ const ownExportedKey = await exportPublicKey(ownKeyPair.publicKey);
224
+ const peerExportedKey = await exportPublicKey(peerPublicKey);
225
+ const appPublicKey = isApp ? ownExportedKey : peerExportedKey;
226
+ const walletPublicKey = isApp ? peerExportedKey : ownExportedKey;
227
+ const salt = createSaltFromPublicKeys(appPublicKey, walletPublicKey);
228
+ // Step 4: Derive 512 bits in a single HKDF call
229
+ const derivedBits = await crypto.subtle.deriveBits({
230
+ name: 'HKDF',
231
+ hash: 'SHA-256',
232
+ salt,
233
+ info: HKDF_INFO
234
+ }, hkdfKey, 512);
235
+ // Step 5: Split into GCM key (first 256 bits) and HMAC key (second 256 bits)
236
+ const gcmKeyBits = derivedBits.slice(0, 32);
237
+ const hmacKeyBits = derivedBits.slice(32, 64);
238
+ // Step 6: Import GCM key
239
+ const encryptionKey = await crypto.subtle.importKey('raw', gcmKeyBits, {
131
240
  name: 'AES-GCM',
132
241
  length: 256
133
- }, true, [
242
+ }, false, [
134
243
  'encrypt',
135
244
  'decrypt'
136
245
  ]);
246
+ // Step 7: Import HMAC key
247
+ const hmacKey = await crypto.subtle.importKey('raw', hmacKeyBits, {
248
+ name: 'HMAC',
249
+ hash: 'SHA-256'
250
+ }, false, [
251
+ 'sign'
252
+ ]);
253
+ // Step 8: Compute verificationHash as HMAC of fixed string
254
+ const verificationHashBytes = await crypto.subtle.sign('HMAC', hmacKey, FINGERPRINT_DATA);
255
+ // Convert to hex string
256
+ const verificationHash = arrayBufferToHex(verificationHashBytes);
257
+ return {
258
+ encryptionKey,
259
+ verificationHash
260
+ };
137
261
  }
138
262
  /**
139
263
  * Encrypts data using AES-256-GCM.
140
264
  *
141
- * The data is JSON serialized before encryption. A random 12-byte IV is
142
- * generated for each encryption operation.
265
+ * A random 12-byte IV is generated for each encryption operation.
143
266
  *
144
267
  * AES-GCM provides both confidentiality and authenticity - any tampering
145
268
  * with the ciphertext will cause decryption to fail.
146
269
  *
147
- * @param key - The AES-GCM key (from {@link deriveSharedKey})
148
- * @param data - The data to encrypt (will be JSON serialized)
270
+ * @param key - The AES-GCM key (from {@link deriveSessionKeys})
271
+ * @param data - The string data to encrypt (caller is responsible for serialization)
149
272
  * @returns The encrypted payload with IV and ciphertext
150
273
  *
151
274
  * @example
152
275
  * ```typescript
153
- * const encrypted = await encrypt(sharedKey, { action: 'transfer', amount: 100 });
276
+ * const encrypted = await encrypt(session.encryptionKey, JSON.stringify({ action: 'transfer', amount: 100 }));
154
277
  * // encrypted.iv and encrypted.ciphertext are base64 strings
155
278
  * ```
156
279
  */ export async function encrypt(key, data) {
157
280
  const iv = crypto.getRandomValues(new Uint8Array(12));
158
- const encoded = new TextEncoder().encode(jsonStringify(data));
281
+ const encoded = new TextEncoder().encode(data);
159
282
  const ciphertext = await crypto.subtle.encrypt({
160
283
  name: 'AES-GCM',
161
284
  iv
@@ -171,7 +294,7 @@
171
294
  * The decrypted data is JSON parsed before returning.
172
295
  *
173
296
  * @typeParam T - The expected type of the decrypted data
174
- * @param key - The AES-GCM key (from {@link deriveSharedKey})
297
+ * @param key - The AES-GCM key (from {@link deriveSessionKeys})
175
298
  * @param payload - The encrypted payload from {@link encrypt}
176
299
  * @returns The decrypted and parsed data
177
300
  *
@@ -179,7 +302,7 @@
179
302
  *
180
303
  * @example
181
304
  * ```typescript
182
- * const decrypted = await decrypt<{ action: string; amount: number }>(sharedKey, encrypted);
305
+ * const decrypted = await decrypt<{ action: string; amount: number }>(session.encryptionKey, encrypted);
183
306
  * console.log(decrypted.action); // 'transfer'
184
307
  * ```
185
308
  */ export async function decrypt(key, payload) {
@@ -215,86 +338,57 @@
215
338
  return bytes.buffer;
216
339
  }
217
340
  /**
218
- * Emoji alphabet for visual verification of shared secrets.
219
- * 32 distinct, easily recognizable emojis for anti-spoofing verification.
341
+ * Converts base64url string to Uint8Array.
220
342
  * @internal
221
- */ const EMOJI_ALPHABET = [
222
- '🔵',
223
- '🟢',
224
- '🔴',
225
- '🟡',
226
- '🟣',
227
- '🟠',
228
- '⚫',
229
- '⚪',
230
- '🌟',
231
- '🌙',
232
- '☀️',
233
- '🌈',
234
- '🔥',
235
- '💧',
236
- '🌸',
237
- '🍀',
238
- '🦋',
239
- '🐬',
240
- '🦊',
241
- '🐼',
242
- '🦁',
243
- '🐯',
244
- '🐸',
245
- '🦉',
246
- '🎵',
247
- '🎨',
248
- '🎯',
249
- '🎲',
250
- '🔔',
251
- '💎',
252
- '🔑',
253
- '🏆'
254
- ];
343
+ */ function base64UrlToBytes(base64url) {
344
+ // Convert base64url to base64
345
+ const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
346
+ // Add padding if needed
347
+ const padded = base64 + '='.repeat((4 - base64.length % 4) % 4);
348
+ const binary = atob(padded);
349
+ const bytes = new Uint8Array(binary.length);
350
+ for(let i = 0; i < binary.length; i++){
351
+ bytes[i] = binary.charCodeAt(i);
352
+ }
353
+ return bytes;
354
+ }
255
355
  /**
256
- * Hashes a shared AES key to a hex string for verification.
257
- *
258
- * This extracts the raw key material and hashes it with SHA-256,
259
- * returning the first 16 bytes as a hex string.
260
- *
261
- * @param sharedKey - The AES-GCM shared key (must be extractable)
262
- * @returns A hex string representation of the hash
263
- *
264
- * @example
265
- * ```typescript
266
- * const hash = await hashSharedSecret(sharedKey);
267
- * const emoji = hashToEmoji(hash);
268
- * ```
269
- */ export async function hashSharedSecret(sharedKey) {
270
- const rawKey = await crypto.subtle.exportKey('raw', sharedKey);
271
- const hash = await crypto.subtle.digest('SHA-256', rawKey);
272
- const bytes = new Uint8Array(hash.slice(0, 16));
356
+ * Converts ArrayBuffer to hex string.
357
+ * @internal
358
+ */ function arrayBufferToHex(buffer) {
359
+ const bytes = new Uint8Array(buffer);
273
360
  return Array.from(bytes).map((b)=>b.toString(16).padStart(2, '0')).join('');
274
361
  }
362
+ /**
363
+ * Default grid size for emoji verification display.
364
+ * 3x3 grid = 9 emojis = 72 bits of security.
365
+ */ export const DEFAULT_EMOJI_GRID_SIZE = 9;
275
366
  /**
276
367
  * Converts a hex hash to an emoji sequence for visual verification.
277
368
  *
278
- * This is used for anti-MITM verification - both the dApp and wallet
279
- * independently compute the same emoji sequence from the shared secret.
369
+ * This is used for verification - both the dApp and wallet
370
+ * independently compute the same emoji sequence from the derived keys.
280
371
  * Users can visually compare the sequences to detect interception.
281
372
  *
282
- * Similar to SAS (Short Authentication String) in ZRTP/Signal.
373
+ * With a 256-emoji alphabet and 9 emojis (3x3 grid), this provides
374
+ * 72 bits of security (9 * 8 = 72 bits), making brute-force attacks
375
+ * computationally infeasible.
283
376
  *
284
- * @param hash - Hex string from {@link hashSharedSecret}
285
- * @param length - Number of emojis to generate (default: 4)
377
+ * @param hash - Hex string from verification hash (64 chars = 32 bytes)
378
+ * @param count - Number of emojis to generate (default: 9 for 3x3 grid)
286
379
  * @returns A string of emojis representing the hash
287
380
  *
288
381
  * @example
289
382
  * ```typescript
290
- * const hash = await hashSharedSecret(sharedKey);
291
- * const emoji = hashToEmoji(hash); // e.g., "🔵🦋🎯🐼"
292
- * // Display to user for verification
383
+ * const session = await deriveSessionKeys(...);
384
+ * const emoji = hashToEmoji(session.verificationHash); // e.g., "🔵🦋🎯🐼🌟🎲🦊🐸💎"
385
+ * // Display as 3x3 grid to user for verification
293
386
  * ```
294
- */ export function hashToEmoji(hash, length = 4) {
295
- const bytes = [];
296
- for(let i = 0; i < hash.length && bytes.length < length; i += 2){
297
- bytes.push(parseInt(hash.slice(i, i + 2), 16));
387
+ */ export function hashToEmoji(hash, count = DEFAULT_EMOJI_GRID_SIZE) {
388
+ const emojis = [];
389
+ for(let i = 0; i < hash.length && emojis.length < count; i += 2){
390
+ const byteValue = parseInt(hash.slice(i, i + 2), 16);
391
+ emojis.push(EMOJI_ALPHABET[byteValue % EMOJI_ALPHABET_SIZE]);
298
392
  }
299
- return bytes.map((b)=>EMOJI_ALPHABET[b % EMOJI_ALPHABET.length]).join('');
393
+ return emojis.join('');
300
394
  }