@aztec/wallet-sdk 0.0.1-commit.7d4e6cd โ 0.0.1-commit.8afd444
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +218 -355
- package/dest/base-wallet/base_wallet.d.ts +33 -10
- package/dest/base-wallet/base_wallet.d.ts.map +1 -1
- package/dest/base-wallet/base_wallet.js +53 -16
- package/dest/crypto.d.ts +73 -27
- package/dest/crypto.d.ts.map +1 -1
- package/dest/crypto.js +219 -41
- package/dest/emoji_alphabet.d.ts +35 -0
- package/dest/emoji_alphabet.d.ts.map +1 -0
- package/dest/emoji_alphabet.js +299 -0
- package/dest/extension/handlers/background_connection_handler.d.ts +158 -0
- package/dest/extension/handlers/background_connection_handler.d.ts.map +1 -0
- package/dest/extension/handlers/background_connection_handler.js +258 -0
- package/dest/extension/handlers/content_script_connection_handler.d.ts +56 -0
- package/dest/extension/handlers/content_script_connection_handler.d.ts.map +1 -0
- package/dest/extension/handlers/content_script_connection_handler.js +174 -0
- package/dest/extension/handlers/index.d.ts +12 -0
- package/dest/extension/handlers/index.d.ts.map +1 -0
- package/dest/extension/handlers/index.js +10 -0
- package/dest/extension/handlers/internal_message_types.d.ts +63 -0
- package/dest/extension/handlers/internal_message_types.d.ts.map +1 -0
- package/dest/extension/handlers/internal_message_types.js +22 -0
- package/dest/extension/provider/extension_provider.d.ts +107 -0
- package/dest/extension/provider/extension_provider.d.ts.map +1 -0
- package/dest/extension/provider/extension_provider.js +160 -0
- package/dest/extension/provider/extension_wallet.d.ts +131 -0
- package/dest/extension/provider/extension_wallet.d.ts.map +1 -0
- package/dest/extension/provider/extension_wallet.js +271 -0
- package/dest/extension/provider/index.d.ts +3 -0
- package/dest/extension/provider/index.d.ts.map +1 -0
- package/dest/{providers/extension โ extension/provider}/index.js +0 -1
- package/dest/manager/index.d.ts +2 -7
- package/dest/manager/index.d.ts.map +1 -1
- package/dest/manager/index.js +0 -4
- package/dest/manager/types.d.ts +108 -5
- package/dest/manager/types.d.ts.map +1 -1
- package/dest/manager/types.js +17 -1
- package/dest/manager/wallet_manager.d.ts +50 -7
- package/dest/manager/wallet_manager.d.ts.map +1 -1
- package/dest/manager/wallet_manager.js +178 -29
- package/dest/types.d.ts +55 -15
- package/dest/types.d.ts.map +1 -1
- package/dest/types.js +10 -2
- package/package.json +11 -10
- package/src/base-wallet/base_wallet.ts +74 -28
- package/src/crypto.ts +263 -47
- package/src/emoji_alphabet.ts +317 -0
- package/src/extension/handlers/background_connection_handler.ts +423 -0
- package/src/extension/handlers/content_script_connection_handler.ts +246 -0
- package/src/extension/handlers/index.ts +25 -0
- package/src/extension/handlers/internal_message_types.ts +69 -0
- package/src/extension/provider/extension_provider.ts +233 -0
- package/src/extension/provider/extension_wallet.ts +321 -0
- package/src/extension/provider/index.ts +7 -0
- package/src/manager/index.ts +3 -9
- package/src/manager/types.ts +112 -4
- package/src/manager/wallet_manager.ts +204 -31
- package/src/types.ts +57 -14
- package/dest/providers/extension/extension_provider.d.ts +0 -17
- package/dest/providers/extension/extension_provider.d.ts.map +0 -1
- package/dest/providers/extension/extension_provider.js +0 -56
- package/dest/providers/extension/extension_wallet.d.ts +0 -95
- package/dest/providers/extension/extension_wallet.d.ts.map +0 -1
- package/dest/providers/extension/extension_wallet.js +0 -225
- package/dest/providers/extension/index.d.ts +0 -5
- package/dest/providers/extension/index.d.ts.map +0 -1
- package/src/providers/extension/extension_provider.ts +0 -72
- package/src/providers/extension/extension_wallet.ts +0 -275
- package/src/providers/extension/index.ts +0 -11
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
|
-
*
|
|
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
|
|
11
|
-
*
|
|
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
|
|
41
|
+
* // Exchange public keys, then derive session keys
|
|
42
|
+
* // App side: isApp = true
|
|
24
43
|
* const importedB = await importPublicKey(publicKeyB);
|
|
25
|
-
* const
|
|
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(
|
|
29
|
-
* const decrypted = await decrypt(
|
|
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 {
|
|
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
|
|
38
|
-
* party's public key using {@link
|
|
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
|
-
'
|
|
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
|
|
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
|
|
121
|
+
* @returns A CryptoKey that can be used with {@link deriveSessionKeys}
|
|
91
122
|
*
|
|
92
123
|
* @example
|
|
93
124
|
* ```typescript
|
|
94
|
-
* //
|
|
95
|
-
* const
|
|
96
|
-
* const
|
|
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
|
-
},
|
|
138
|
+
}, true, []);
|
|
108
139
|
}
|
|
109
140
|
/**
|
|
110
|
-
*
|
|
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.
|
|
111
145
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
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.
|
|
114
164
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
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;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
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")
|
|
193
|
+
*
|
|
194
|
+
* The keys are derived using a single HKDF call that produces 512 bits,
|
|
195
|
+
* then split into the two keys.
|
|
196
|
+
*
|
|
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
|
-
* //
|
|
122
|
-
* const
|
|
123
|
-
*
|
|
124
|
-
*
|
|
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
|
|
127
|
-
|
|
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:
|
|
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
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
|
-
*
|
|
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
|
|
148
|
-
* @param data - The data to encrypt (
|
|
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(
|
|
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(
|
|
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
|
|
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 }>(
|
|
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) {
|
|
@@ -214,3 +337,58 @@
|
|
|
214
337
|
}
|
|
215
338
|
return bytes.buffer;
|
|
216
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Converts base64url string to Uint8Array.
|
|
342
|
+
* @internal
|
|
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
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Converts ArrayBuffer to hex string.
|
|
357
|
+
* @internal
|
|
358
|
+
*/ function arrayBufferToHex(buffer) {
|
|
359
|
+
const bytes = new Uint8Array(buffer);
|
|
360
|
+
return Array.from(bytes).map((b)=>b.toString(16).padStart(2, '0')).join('');
|
|
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;
|
|
366
|
+
/**
|
|
367
|
+
* Converts a hex hash to an emoji sequence for visual verification.
|
|
368
|
+
*
|
|
369
|
+
* This is used for verification - both the dApp and wallet
|
|
370
|
+
* independently compute the same emoji sequence from the derived keys.
|
|
371
|
+
* Users can visually compare the sequences to detect interception.
|
|
372
|
+
*
|
|
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.
|
|
376
|
+
*
|
|
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)
|
|
379
|
+
* @returns A string of emojis representing the hash
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* ```typescript
|
|
383
|
+
* const session = await deriveSessionKeys(...);
|
|
384
|
+
* const emoji = hashToEmoji(session.verificationHash); // e.g., "๐ต๐ฆ๐ฏ๐ผ๐๐ฒ๐ฆ๐ธ๐"
|
|
385
|
+
* // Display as 3x3 grid to user for verification
|
|
386
|
+
* ```
|
|
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]);
|
|
392
|
+
}
|
|
393
|
+
return emojis.join('');
|
|
394
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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==
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,299 @@
|
|
|
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
|
+
* 256 distinct emojis for visual verification.
|
|
22
|
+
* Index directly maps to byte value (0-255) for simple lookup.
|
|
23
|
+
*/ export const EMOJI_ALPHABET = [
|
|
24
|
+
// 0-15: Faces & figures (16)
|
|
25
|
+
'๐',
|
|
26
|
+
'๐',
|
|
27
|
+
'๐ค',
|
|
28
|
+
'๐ป',
|
|
29
|
+
'๐',
|
|
30
|
+
'๐ฝ',
|
|
31
|
+
'๐คก',
|
|
32
|
+
'๐',
|
|
33
|
+
'๐บ',
|
|
34
|
+
'๐',
|
|
35
|
+
'๐พ',
|
|
36
|
+
'๐ฟ',
|
|
37
|
+
'๐ค ',
|
|
38
|
+
'๐ฅธ',
|
|
39
|
+
'๐ง',
|
|
40
|
+
'๐ต',
|
|
41
|
+
// 16-47: Animals (32)
|
|
42
|
+
'๐ถ',
|
|
43
|
+
'๐ฑ',
|
|
44
|
+
'๐ธ',
|
|
45
|
+
'๐ต',
|
|
46
|
+
'๐ฆ',
|
|
47
|
+
'๐ป',
|
|
48
|
+
'๐ฆ',
|
|
49
|
+
'๐ฏ',
|
|
50
|
+
'๐ฎ',
|
|
51
|
+
'๐ท',
|
|
52
|
+
'๐',
|
|
53
|
+
'๐ง',
|
|
54
|
+
'๐ฆ',
|
|
55
|
+
'๐ฆ',
|
|
56
|
+
'๐บ',
|
|
57
|
+
'๐ฆ',
|
|
58
|
+
'๐',
|
|
59
|
+
'๐ฆ',
|
|
60
|
+
'๐',
|
|
61
|
+
'๐ฆ',
|
|
62
|
+
'๐',
|
|
63
|
+
'๐ฆ',
|
|
64
|
+
'๐ข',
|
|
65
|
+
'๐ฆ',
|
|
66
|
+
'๐',
|
|
67
|
+
'๐ฆ
',
|
|
68
|
+
'๐ฆ',
|
|
69
|
+
'๐ณ',
|
|
70
|
+
'๐ฆญ',
|
|
71
|
+
'๐ฆฉ',
|
|
72
|
+
'๐ฆ',
|
|
73
|
+
'๐',
|
|
74
|
+
// 48-63: Nature & weather (16)
|
|
75
|
+
'๐ต',
|
|
76
|
+
'๐ด',
|
|
77
|
+
'๐ฒ',
|
|
78
|
+
'๐ณ',
|
|
79
|
+
'๐',
|
|
80
|
+
'๐',
|
|
81
|
+
'๐',
|
|
82
|
+
'๐',
|
|
83
|
+
'๐',
|
|
84
|
+
'๐',
|
|
85
|
+
'๐ฅ',
|
|
86
|
+
'๐ง',
|
|
87
|
+
'๐จ',
|
|
88
|
+
'๐ข',
|
|
89
|
+
'๐',
|
|
90
|
+
'๐ค',
|
|
91
|
+
// 64-95: Food & drinks (32)
|
|
92
|
+
'๐',
|
|
93
|
+
'๐',
|
|
94
|
+
'๐',
|
|
95
|
+
'๐',
|
|
96
|
+
'๐',
|
|
97
|
+
'๐',
|
|
98
|
+
'๐ฅ',
|
|
99
|
+
'๐ฝ',
|
|
100
|
+
'๐ฅ',
|
|
101
|
+
'๐',
|
|
102
|
+
'๐',
|
|
103
|
+
'๐ฎ',
|
|
104
|
+
'๐ฃ',
|
|
105
|
+
'๐ช',
|
|
106
|
+
'๐',
|
|
107
|
+
'๐ฉ',
|
|
108
|
+
'๐บ',
|
|
109
|
+
'๐ท',
|
|
110
|
+
'๐ธ',
|
|
111
|
+
'๐ซ',
|
|
112
|
+
'๐ง',
|
|
113
|
+
'๐ฅ',
|
|
114
|
+
'๐ต',
|
|
115
|
+
'๐ง',
|
|
116
|
+
'๐ซ',
|
|
117
|
+
'๐ฌ',
|
|
118
|
+
'๐ญ',
|
|
119
|
+
'๐ฅจ',
|
|
120
|
+
'๐ฅฏ',
|
|
121
|
+
'๐ฅ',
|
|
122
|
+
'๐ง',
|
|
123
|
+
'๐ฟ',
|
|
124
|
+
// 96-127: Symbols & colors (32)
|
|
125
|
+
'๐ฏ',
|
|
126
|
+
'๐',
|
|
127
|
+
'๐ฅ',
|
|
128
|
+
'๐ซ',
|
|
129
|
+
'๐',
|
|
130
|
+
'๐ซจ',
|
|
131
|
+
'๐ง',
|
|
132
|
+
'๐',
|
|
133
|
+
'๐ด',
|
|
134
|
+
'๐ต',
|
|
135
|
+
'๐ข',
|
|
136
|
+
'๐ก',
|
|
137
|
+
'๐ฃ',
|
|
138
|
+
'โฌ',
|
|
139
|
+
'โฌ',
|
|
140
|
+
'๐ค',
|
|
141
|
+
'๐',
|
|
142
|
+
'๐',
|
|
143
|
+
'๐',
|
|
144
|
+
'๐งก',
|
|
145
|
+
'๐ค',
|
|
146
|
+
'๐ค',
|
|
147
|
+
'๐',
|
|
148
|
+
'๐',
|
|
149
|
+
'๐',
|
|
150
|
+
'๐',
|
|
151
|
+
'๐ฉ',
|
|
152
|
+
'๐งข',
|
|
153
|
+
'๐ชญ',
|
|
154
|
+
'๐',
|
|
155
|
+
'๐',
|
|
156
|
+
'๐ท',
|
|
157
|
+
// 128-159: Electronics & tools (32)
|
|
158
|
+
'๐',
|
|
159
|
+
'๐ฑ',
|
|
160
|
+
'๐ป',
|
|
161
|
+
'๐ฅ',
|
|
162
|
+
'๐ท',
|
|
163
|
+
'๐ฅ',
|
|
164
|
+
'๐บ',
|
|
165
|
+
'๐ป',
|
|
166
|
+
'๐',
|
|
167
|
+
'๐ก',
|
|
168
|
+
'๐',
|
|
169
|
+
'๐จ',
|
|
170
|
+
'๐ ',
|
|
171
|
+
'๐งฒ',
|
|
172
|
+
'๐งช',
|
|
173
|
+
'๐ฌ',
|
|
174
|
+
'๐ง',
|
|
175
|
+
'๐ช',
|
|
176
|
+
'๐ฉ',
|
|
177
|
+
'๐ช',
|
|
178
|
+
'๐ช',
|
|
179
|
+
'๐งน',
|
|
180
|
+
'๐ชฃ',
|
|
181
|
+
'๐งด',
|
|
182
|
+
'๐',
|
|
183
|
+
'๐ฝ',
|
|
184
|
+
'๐ช',
|
|
185
|
+
'๐',
|
|
186
|
+
'๐ฏ',
|
|
187
|
+
'๐ชค',
|
|
188
|
+
'๐งฏ',
|
|
189
|
+
'๐ช',
|
|
190
|
+
// 160-175: Transport (16)
|
|
191
|
+
'๐',
|
|
192
|
+
'๐',
|
|
193
|
+
'๐',
|
|
194
|
+
'๐',
|
|
195
|
+
'๐',
|
|
196
|
+
'๐',
|
|
197
|
+
'๐ฒ',
|
|
198
|
+
'๐',
|
|
199
|
+
'๐ฉ',
|
|
200
|
+
'๐',
|
|
201
|
+
'๐ธ',
|
|
202
|
+
'๐',
|
|
203
|
+
'๐ข',
|
|
204
|
+
'๐ค',
|
|
205
|
+
'๐',
|
|
206
|
+
'๐ ',
|
|
207
|
+
// 176-191: Sports & games (16)
|
|
208
|
+
'๐ฅ
',
|
|
209
|
+
'๐',
|
|
210
|
+
'๐',
|
|
211
|
+
'๐',
|
|
212
|
+
'๐',
|
|
213
|
+
'๐ฑ',
|
|
214
|
+
'๐ฏ',
|
|
215
|
+
'๐ณ',
|
|
216
|
+
'๐ฒ',
|
|
217
|
+
'๐ฎ',
|
|
218
|
+
'๐น',
|
|
219
|
+
'๐งฉ',
|
|
220
|
+
'๐ด',
|
|
221
|
+
'๐ฐ',
|
|
222
|
+
'๐',
|
|
223
|
+
'๐',
|
|
224
|
+
// 192-207: Music & arts (16)
|
|
225
|
+
'๐ธ',
|
|
226
|
+
'๐น',
|
|
227
|
+
'๐บ',
|
|
228
|
+
'๐ป',
|
|
229
|
+
'๐ฅ',
|
|
230
|
+
'๐ท',
|
|
231
|
+
'๐ค',
|
|
232
|
+
'๐ง',
|
|
233
|
+
'๐จ',
|
|
234
|
+
'๐',
|
|
235
|
+
'๐ญ',
|
|
236
|
+
'๐ช',
|
|
237
|
+
'๐ฌ',
|
|
238
|
+
'๐ผ',
|
|
239
|
+
'๐ช',
|
|
240
|
+
'๐ช',
|
|
241
|
+
// 208-223: Buildings & places (16)
|
|
242
|
+
'๐ ',
|
|
243
|
+
'๐ฐ',
|
|
244
|
+
'๐ผ',
|
|
245
|
+
'๐ฝ',
|
|
246
|
+
'๐',
|
|
247
|
+
'๐',
|
|
248
|
+
'๐',
|
|
249
|
+
'๐ฏ',
|
|
250
|
+
'๐ข',
|
|
251
|
+
'๐ก',
|
|
252
|
+
'๐',
|
|
253
|
+
'๐',
|
|
254
|
+
'๐ป',
|
|
255
|
+
'๐',
|
|
256
|
+
'๐',
|
|
257
|
+
'๐',
|
|
258
|
+
// 224-239: Science & medical (16)
|
|
259
|
+
'๐ฆ',
|
|
260
|
+
'๐งณ',
|
|
261
|
+
'๐ฐ',
|
|
262
|
+
'๐ฆ',
|
|
263
|
+
'๐ชซ',
|
|
264
|
+
'๐ก',
|
|
265
|
+
'๐ญ',
|
|
266
|
+
'๐ฎ',
|
|
267
|
+
'๐งฟ',
|
|
268
|
+
'๐ฟ',
|
|
269
|
+
'๐ซ',
|
|
270
|
+
'๐',
|
|
271
|
+
'๐ฉบ',
|
|
272
|
+
'๐ฉน',
|
|
273
|
+
'๐งฌ',
|
|
274
|
+
'๐ฆ ',
|
|
275
|
+
// 240-255: Miscellaneous (16)
|
|
276
|
+
'๐',
|
|
277
|
+
'๐',
|
|
278
|
+
'๐',
|
|
279
|
+
'๐',
|
|
280
|
+
'๐ช
',
|
|
281
|
+
'๐งง',
|
|
282
|
+
'๐จ',
|
|
283
|
+
'๐',
|
|
284
|
+
'๐',
|
|
285
|
+
'๐',
|
|
286
|
+
'๐ชก',
|
|
287
|
+
'๐',
|
|
288
|
+
'๐',
|
|
289
|
+
'๐',
|
|
290
|
+
'๐งธ',
|
|
291
|
+
'๐ช'
|
|
292
|
+
];
|
|
293
|
+
/**
|
|
294
|
+
* Number of emojis in the alphabet.
|
|
295
|
+
* Must be a power of 2 for clean bit mapping (256 = 2^8).
|
|
296
|
+
*/ export const EMOJI_ALPHABET_SIZE = 256;
|
|
297
|
+
/**
|
|
298
|
+
* Bits of entropy per emoji (log2 of alphabet size).
|
|
299
|
+
*/ export const BITS_PER_EMOJI = 8;
|