@bananalink-sdk/protocol 1.2.7

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 (158) hide show
  1. package/README.md +604 -0
  2. package/dist/chunk-32OWUOZ3.js +308 -0
  3. package/dist/chunk-32OWUOZ3.js.map +1 -0
  4. package/dist/chunk-65HNHRJK.cjs +123 -0
  5. package/dist/chunk-65HNHRJK.cjs.map +1 -0
  6. package/dist/chunk-7KYDLL3B.js +480 -0
  7. package/dist/chunk-7KYDLL3B.js.map +1 -0
  8. package/dist/chunk-A6FLEJ7R.cjs +62 -0
  9. package/dist/chunk-A6FLEJ7R.cjs.map +1 -0
  10. package/dist/chunk-CUJK7ZTS.js +217 -0
  11. package/dist/chunk-CUJK7ZTS.js.map +1 -0
  12. package/dist/chunk-GI3BUPIH.cjs +236 -0
  13. package/dist/chunk-GI3BUPIH.cjs.map +1 -0
  14. package/dist/chunk-JXHV66Q4.js +106 -0
  15. package/dist/chunk-JXHV66Q4.js.map +1 -0
  16. package/dist/chunk-KNGZKGRS.cjs +552 -0
  17. package/dist/chunk-KNGZKGRS.cjs.map +1 -0
  18. package/dist/chunk-LELPCIE7.js +840 -0
  19. package/dist/chunk-LELPCIE7.js.map +1 -0
  20. package/dist/chunk-MCZG7QEM.cjs +310 -0
  21. package/dist/chunk-MCZG7QEM.cjs.map +1 -0
  22. package/dist/chunk-TCVKC227.js +56 -0
  23. package/dist/chunk-TCVKC227.js.map +1 -0
  24. package/dist/chunk-VXLUSU5B.cjs +856 -0
  25. package/dist/chunk-VXLUSU5B.cjs.map +1 -0
  26. package/dist/chunk-WCQVDF3K.js +12 -0
  27. package/dist/chunk-WCQVDF3K.js.map +1 -0
  28. package/dist/chunk-WGEGR3DF.cjs +15 -0
  29. package/dist/chunk-WGEGR3DF.cjs.map +1 -0
  30. package/dist/client-session-claim-3QF3noOr.d.ts +197 -0
  31. package/dist/client-session-claim-C4lUik3b.d.cts +197 -0
  32. package/dist/core-DMhuNfoz.d.cts +62 -0
  33. package/dist/core-DMhuNfoz.d.ts +62 -0
  34. package/dist/crypto/providers/noble-provider.cjs +14 -0
  35. package/dist/crypto/providers/noble-provider.cjs.map +1 -0
  36. package/dist/crypto/providers/noble-provider.d.cts +30 -0
  37. package/dist/crypto/providers/noble-provider.d.ts +30 -0
  38. package/dist/crypto/providers/noble-provider.js +5 -0
  39. package/dist/crypto/providers/noble-provider.js.map +1 -0
  40. package/dist/crypto/providers/node-provider.cjs +308 -0
  41. package/dist/crypto/providers/node-provider.cjs.map +1 -0
  42. package/dist/crypto/providers/node-provider.d.cts +32 -0
  43. package/dist/crypto/providers/node-provider.d.ts +32 -0
  44. package/dist/crypto/providers/node-provider.js +306 -0
  45. package/dist/crypto/providers/node-provider.js.map +1 -0
  46. package/dist/crypto/providers/quickcrypto-provider.cjs +339 -0
  47. package/dist/crypto/providers/quickcrypto-provider.cjs.map +1 -0
  48. package/dist/crypto/providers/quickcrypto-provider.d.cts +34 -0
  49. package/dist/crypto/providers/quickcrypto-provider.d.ts +34 -0
  50. package/dist/crypto/providers/quickcrypto-provider.js +337 -0
  51. package/dist/crypto/providers/quickcrypto-provider.js.map +1 -0
  52. package/dist/crypto/providers/webcrypto-provider.cjs +310 -0
  53. package/dist/crypto/providers/webcrypto-provider.cjs.map +1 -0
  54. package/dist/crypto/providers/webcrypto-provider.d.cts +30 -0
  55. package/dist/crypto/providers/webcrypto-provider.d.ts +30 -0
  56. package/dist/crypto/providers/webcrypto-provider.js +308 -0
  57. package/dist/crypto/providers/webcrypto-provider.js.map +1 -0
  58. package/dist/crypto-BUS06Qz-.d.cts +40 -0
  59. package/dist/crypto-BUS06Qz-.d.ts +40 -0
  60. package/dist/crypto-export.cjs +790 -0
  61. package/dist/crypto-export.cjs.map +1 -0
  62. package/dist/crypto-export.d.cts +257 -0
  63. package/dist/crypto-export.d.ts +257 -0
  64. package/dist/crypto-export.js +709 -0
  65. package/dist/crypto-export.js.map +1 -0
  66. package/dist/crypto-provider-deYoVIxi.d.cts +36 -0
  67. package/dist/crypto-provider-deYoVIxi.d.ts +36 -0
  68. package/dist/index.cjs +615 -0
  69. package/dist/index.cjs.map +1 -0
  70. package/dist/index.d.cts +379 -0
  71. package/dist/index.d.ts +379 -0
  72. package/dist/index.js +504 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/schemas-export.cjs +294 -0
  75. package/dist/schemas-export.cjs.map +1 -0
  76. package/dist/schemas-export.d.cts +1598 -0
  77. package/dist/schemas-export.d.ts +1598 -0
  78. package/dist/schemas-export.js +5 -0
  79. package/dist/schemas-export.js.map +1 -0
  80. package/dist/siwe-export.cjs +237 -0
  81. package/dist/siwe-export.cjs.map +1 -0
  82. package/dist/siwe-export.d.cts +27 -0
  83. package/dist/siwe-export.d.ts +27 -0
  84. package/dist/siwe-export.js +228 -0
  85. package/dist/siwe-export.js.map +1 -0
  86. package/dist/testing.cjs +54 -0
  87. package/dist/testing.cjs.map +1 -0
  88. package/dist/testing.d.cts +20 -0
  89. package/dist/testing.d.ts +20 -0
  90. package/dist/testing.js +51 -0
  91. package/dist/testing.js.map +1 -0
  92. package/dist/validation-export.cjs +359 -0
  93. package/dist/validation-export.cjs.map +1 -0
  94. package/dist/validation-export.d.cts +3 -0
  95. package/dist/validation-export.d.ts +3 -0
  96. package/dist/validation-export.js +6 -0
  97. package/dist/validation-export.js.map +1 -0
  98. package/dist/validators-export.cjs +73 -0
  99. package/dist/validators-export.cjs.map +1 -0
  100. package/dist/validators-export.d.cts +37 -0
  101. package/dist/validators-export.d.ts +37 -0
  102. package/dist/validators-export.js +4 -0
  103. package/dist/validators-export.js.map +1 -0
  104. package/package.json +140 -0
  105. package/src/constants/index.ts +205 -0
  106. package/src/crypto/context.ts +228 -0
  107. package/src/crypto/diagnostics.ts +772 -0
  108. package/src/crypto/errors.ts +114 -0
  109. package/src/crypto/index.ts +89 -0
  110. package/src/crypto/payload-handler.ts +102 -0
  111. package/src/crypto/providers/compliance-provider.ts +579 -0
  112. package/src/crypto/providers/factory.ts +204 -0
  113. package/src/crypto/providers/index.ts +44 -0
  114. package/src/crypto/providers/noble-provider.ts +392 -0
  115. package/src/crypto/providers/node-provider.ts +433 -0
  116. package/src/crypto/providers/quickcrypto-provider.ts +483 -0
  117. package/src/crypto/providers/registry.ts +129 -0
  118. package/src/crypto/providers/webcrypto-provider.ts +364 -0
  119. package/src/crypto/session-security.ts +185 -0
  120. package/src/crypto/types.ts +93 -0
  121. package/src/crypto/utils.ts +190 -0
  122. package/src/crypto-export.ts +21 -0
  123. package/src/index.ts +38 -0
  124. package/src/schemas/auth.ts +60 -0
  125. package/src/schemas/client-messages.ts +57 -0
  126. package/src/schemas/core.ts +144 -0
  127. package/src/schemas/crypto.ts +65 -0
  128. package/src/schemas/discovery.ts +79 -0
  129. package/src/schemas/index.ts +239 -0
  130. package/src/schemas/relay-messages.ts +45 -0
  131. package/src/schemas/wallet-messages.ts +177 -0
  132. package/src/schemas-export.ts +23 -0
  133. package/src/siwe-export.ts +27 -0
  134. package/src/testing.ts +71 -0
  135. package/src/types/auth.ts +60 -0
  136. package/src/types/client-messages.ts +84 -0
  137. package/src/types/core.ts +131 -0
  138. package/src/types/crypto-provider.ts +264 -0
  139. package/src/types/crypto.ts +90 -0
  140. package/src/types/discovery.ts +50 -0
  141. package/src/types/errors.ts +87 -0
  142. package/src/types/index.ts +197 -0
  143. package/src/types/post-auth-operations.ts +363 -0
  144. package/src/types/providers.ts +72 -0
  145. package/src/types/relay-messages.ts +60 -0
  146. package/src/types/request-lifecycle.ts +161 -0
  147. package/src/types/signing-operations.ts +99 -0
  148. package/src/types/wallet-messages.ts +251 -0
  149. package/src/utils/client-session-claim.ts +188 -0
  150. package/src/utils/index.ts +54 -0
  151. package/src/utils/public-keys.ts +49 -0
  152. package/src/utils/siwe.ts +362 -0
  153. package/src/utils/url-decoding.ts +126 -0
  154. package/src/utils/url-encoding.ts +144 -0
  155. package/src/utils/wallet-session-claim.ts +188 -0
  156. package/src/validation-export.ts +32 -0
  157. package/src/validators/index.ts +222 -0
  158. package/src/validators-export.ts +8 -0
@@ -0,0 +1,483 @@
1
+ import type { Logger } from '@bananalink-sdk/logger';
2
+ import type { CryptoProvider, CryptoKeyLike, ProviderKeyPair } from '../../types/crypto-provider';
3
+ import { NobleCryptoProvider } from './noble-provider';
4
+ import { registerCryptoProvider } from './registry';
5
+
6
+ // Global require function type for React Native environments
7
+ type RequireFunction = (id: string) => unknown;
8
+ type GlobalWithRequire = typeof globalThis & {
9
+ require?: RequireFunction;
10
+ };
11
+
12
+ /**
13
+ * Type guard to check if a key has a data property (from Noble provider)
14
+ */
15
+ function hasKeyData(key: CryptoKeyLike): key is CryptoKeyLike & { data: Uint8Array } {
16
+ return 'data' in key && key.data instanceof Uint8Array;
17
+ }
18
+
19
+ // Type definitions for react-native-quick-crypto Node.js-like API
20
+ interface QuickCryptoHash {
21
+ update(data: Uint8Array): void;
22
+ digest(): Uint8Array;
23
+ }
24
+
25
+ interface QuickCryptoCipher {
26
+ update(data: Uint8Array): Uint8Array;
27
+ final(): Uint8Array;
28
+ getAuthTag?(): Uint8Array;
29
+ }
30
+
31
+ interface QuickCryptoDecipher {
32
+ update(data: Uint8Array): Uint8Array;
33
+ final(): Uint8Array;
34
+ setAuthTag?(authTag: Uint8Array): void;
35
+ }
36
+
37
+ interface QuickCrypto {
38
+ randomBytes(size: number): Uint8Array;
39
+ createHash(algorithm: string): QuickCryptoHash;
40
+ createHmac(algorithm: string, key: Uint8Array): QuickCryptoHash;
41
+ createCipheriv(algorithm: string, key: Uint8Array, iv: Uint8Array): QuickCryptoCipher;
42
+ createDecipheriv(algorithm: string, key: Uint8Array, iv: Uint8Array): QuickCryptoDecipher;
43
+ }
44
+
45
+ /**
46
+ * QuickCrypto key wrapper to implement CryptoKeyLike interface
47
+ */
48
+ class QuickCryptoKey implements CryptoKeyLike {
49
+ constructor(
50
+ public readonly data: Uint8Array,
51
+ public readonly type: 'public' | 'private' | 'secret',
52
+ public readonly algorithm: string,
53
+ public readonly extractable: boolean = true,
54
+ public readonly usages: readonly string[] = []
55
+ ) {}
56
+ }
57
+
58
+ /**
59
+ * Check if react-native-quick-crypto is available
60
+ */
61
+ function isQuickCryptoAvailable(): boolean {
62
+ try {
63
+ // Use globalThis.require for React Native compatibility
64
+ const global = globalThis as GlobalWithRequire;
65
+ if (!global.require) return false;
66
+
67
+ const quickCrypto = global.require('react-native-quick-crypto') as QuickCrypto | undefined;
68
+ if (!quickCrypto) return false;
69
+
70
+ return (
71
+ typeof quickCrypto.randomBytes === 'function' &&
72
+ typeof quickCrypto.createHash === 'function' &&
73
+ typeof quickCrypto.createHmac === 'function' &&
74
+ typeof quickCrypto.createCipheriv === 'function'
75
+ );
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * React Native QuickCrypto implementation of CryptoProvider
83
+ * Hybrid approach: uses Noble for ECDH operations and QuickCrypto for AES/HMAC/hashing
84
+ * This provides optimal performance while maintaining full compatibility
85
+ */
86
+ export class QuickCryptoProvider implements CryptoProvider {
87
+ public readonly name = 'QuickCrypto';
88
+ public readonly isAvailable: boolean;
89
+
90
+ private readonly logger?: Logger;
91
+ private readonly quickCrypto?: QuickCrypto;
92
+ private readonly nobleProvider: NobleCryptoProvider;
93
+
94
+ constructor(logger?: Logger) {
95
+ this.logger = logger?.child({ component: 'QuickCryptoProvider' });
96
+ this.isAvailable = isQuickCryptoAvailable();
97
+
98
+ if (this.isAvailable) {
99
+ try {
100
+ const global = globalThis as GlobalWithRequire;
101
+ this.quickCrypto = global.require?.('react-native-quick-crypto') as QuickCrypto | undefined;
102
+ if (this.quickCrypto) {
103
+ this.logger?.debug('QuickCrypto initialized successfully');
104
+ } else {
105
+ this.logger?.warn('QuickCrypto require returned undefined');
106
+ // Mark as unavailable if we can't actually use it
107
+ (this as { isAvailable: boolean }).isAvailable = false;
108
+ }
109
+ } catch (error) {
110
+ this.logger?.error('Failed to initialize QuickCrypto', {
111
+ error: {
112
+ message: error instanceof Error ? error.message : String(error),
113
+ stack: error instanceof Error ? error.stack : undefined
114
+ }
115
+ });
116
+ // Mark as unavailable if we can't actually use it
117
+ (this as { isAvailable: boolean }).isAvailable = false;
118
+ }
119
+ }
120
+
121
+ // Always initialize Noble provider as fallback for ECDH operations
122
+ this.nobleProvider = new NobleCryptoProvider(logger);
123
+ }
124
+
125
+ /**
126
+ * Generate ECDH P-256 key pair using Noble (QuickCrypto doesn't support P-256 ECDH)
127
+ */
128
+ async generateKeyPair(): Promise<ProviderKeyPair> {
129
+ this.logger?.debug('Generating ECDH P-256 key pair using Noble (fallback)');
130
+
131
+ // Use Noble for key generation as QuickCrypto doesn't support P-256 ECDH
132
+ const nobleKeyPair = await this.nobleProvider.generateKeyPair();
133
+
134
+ // Wrap in QuickCryptoKey for consistency
135
+ if (!hasKeyData(nobleKeyPair.privateKey) || !hasKeyData(nobleKeyPair.publicKey)) {
136
+ throw new Error('Noble keys missing required data property');
137
+ }
138
+
139
+ return {
140
+ privateKey: new QuickCryptoKey(
141
+ nobleKeyPair.privateKey.data,
142
+ 'private',
143
+ 'ECDH-P256',
144
+ true,
145
+ ['deriveKey']
146
+ ),
147
+ publicKey: new QuickCryptoKey(
148
+ nobleKeyPair.publicKey.data,
149
+ 'public',
150
+ 'ECDH-P256',
151
+ true,
152
+ []
153
+ )
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Export public key to ArrayBuffer (delegate to Noble)
159
+ */
160
+ exportPublicKey(publicKey: CryptoKeyLike): Promise<ArrayBuffer> {
161
+ return this.nobleProvider.exportPublicKey(publicKey);
162
+ }
163
+
164
+ /**
165
+ * Export private key to ArrayBuffer (delegate to Noble)
166
+ */
167
+ exportPrivateKey(privateKey: CryptoKeyLike): Promise<ArrayBuffer> {
168
+ return this.nobleProvider.exportPrivateKey(privateKey);
169
+ }
170
+
171
+ /**
172
+ * Import public key from ArrayBuffer (delegate to Noble)
173
+ */
174
+ async importPublicKey(keyData: ArrayBuffer): Promise<CryptoKeyLike> {
175
+ const nobleKey = await this.nobleProvider.importPublicKey(keyData);
176
+
177
+ if (!hasKeyData(nobleKey)) {
178
+ throw new Error('Noble public key missing required data property');
179
+ }
180
+
181
+ // Wrap in QuickCryptoKey for consistency
182
+ return new QuickCryptoKey(
183
+ nobleKey.data,
184
+ 'public',
185
+ 'ECDH-P256',
186
+ true,
187
+ []
188
+ );
189
+ }
190
+
191
+ /**
192
+ * Import private key from ArrayBuffer (delegate to Noble)
193
+ */
194
+ async importPrivateKey(keyData: ArrayBuffer): Promise<CryptoKeyLike> {
195
+ const nobleKey = await this.nobleProvider.importPrivateKey(keyData);
196
+
197
+ if (!hasKeyData(nobleKey)) {
198
+ throw new Error('Noble private key missing required data property');
199
+ }
200
+
201
+ // Wrap in QuickCryptoKey for consistency
202
+ return new QuickCryptoKey(
203
+ nobleKey.data,
204
+ 'private',
205
+ 'ECDH-P256',
206
+ true,
207
+ ['deriveKey']
208
+ );
209
+ }
210
+
211
+ /**
212
+ * Derive shared secret from ECDH key agreement (delegate to Noble)
213
+ */
214
+ async deriveSharedSecret(privateKey: CryptoKeyLike, publicKey: CryptoKeyLike): Promise<CryptoKeyLike> {
215
+ this.logger?.debug('Deriving shared secret using Noble (fallback)');
216
+
217
+ const sharedSecret = await this.nobleProvider.deriveSharedSecret(privateKey, publicKey);
218
+
219
+ if (!hasKeyData(sharedSecret)) {
220
+ throw new Error('Noble shared secret missing required data property');
221
+ }
222
+
223
+ // Wrap in QuickCryptoKey for consistency
224
+ return new QuickCryptoKey(
225
+ sharedSecret.data,
226
+ 'secret',
227
+ 'AES-GCM',
228
+ true,
229
+ ['encrypt', 'decrypt']
230
+ );
231
+ }
232
+
233
+ /**
234
+ * Derive AES-GCM key from shared secret using HKDF (delegate to Noble)
235
+ */
236
+ async deriveEncryptionKey(sharedSecret: CryptoKeyLike, salt: ArrayBuffer, info: ArrayBuffer): Promise<CryptoKeyLike> {
237
+ const encryptionKey = await this.nobleProvider.deriveEncryptionKey(sharedSecret, salt, info);
238
+
239
+ if (!hasKeyData(encryptionKey)) {
240
+ throw new Error('Noble encryption key missing required data property');
241
+ }
242
+
243
+ // Wrap in QuickCryptoKey for consistency
244
+ return new QuickCryptoKey(
245
+ encryptionKey.data,
246
+ 'secret',
247
+ 'AES-GCM',
248
+ true,
249
+ ['encrypt', 'decrypt']
250
+ );
251
+ }
252
+
253
+ /**
254
+ * Generate random bytes using QuickCrypto (58x faster than crypto-browserify)
255
+ */
256
+ randomBytes(length: number): ArrayBuffer {
257
+ if (!this.quickCrypto || !this.isAvailable) {
258
+ this.logger?.debug('QuickCrypto not available, falling back to Noble for randomBytes');
259
+ return this.nobleProvider.randomBytes(length);
260
+ }
261
+
262
+ try {
263
+ const bytes = this.quickCrypto.randomBytes(length);
264
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
265
+ } catch (error) {
266
+ this.logger?.error('QuickCrypto randomBytes failed, falling back to Noble', {
267
+ error: {
268
+ message: error instanceof Error ? error.message : String(error)
269
+ }
270
+ });
271
+ return this.nobleProvider.randomBytes(length);
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Encrypt data using AES-GCM with QuickCrypto (much faster than Noble)
277
+ */
278
+ async encrypt(key: CryptoKeyLike, data: ArrayBuffer, iv: ArrayBuffer): Promise<ArrayBuffer> {
279
+ if (!this.quickCrypto || !this.isAvailable) {
280
+ this.logger?.debug('QuickCrypto not available, falling back to Noble for encryption');
281
+ return this.nobleProvider.encrypt(key, data, iv);
282
+ }
283
+
284
+ this.logger?.debug('Encrypting data with AES-GCM using QuickCrypto', {
285
+ dataSize: data.byteLength,
286
+ ivSize: iv.byteLength
287
+ });
288
+
289
+ const secretKey = key as QuickCryptoKey;
290
+
291
+ if (secretKey.type !== 'secret') {
292
+ const error = new Error('Expected secret key');
293
+ this.logger?.error('Encryption failed - invalid key type', {
294
+ actualType: secretKey.type
295
+ });
296
+ throw error;
297
+ }
298
+
299
+ try {
300
+ // Use QuickCrypto's Node.js-compatible AES-GCM implementation
301
+ const cipher = this.quickCrypto.createCipheriv('aes-256-gcm', secretKey.data, new Uint8Array(iv));
302
+
303
+ const encrypted = cipher.update(new Uint8Array(data));
304
+ const final = cipher.final();
305
+
306
+ // Get authentication tag (GCM mode)
307
+ const authTag = cipher.getAuthTag?.() || new Uint8Array(16);
308
+
309
+ // Combine encrypted data + auth tag (standard AES-GCM format)
310
+ const result = new Uint8Array(encrypted.length + final.length + authTag.length);
311
+ result.set(encrypted, 0);
312
+ result.set(final, encrypted.length);
313
+ result.set(authTag, encrypted.length + final.length);
314
+
315
+ this.logger?.debug('Encryption completed using QuickCrypto', {
316
+ ciphertextSize: result.byteLength
317
+ });
318
+
319
+ return result.buffer.slice(result.byteOffset, result.byteOffset + result.byteLength);
320
+ } catch (error) {
321
+ this.logger?.error('QuickCrypto encryption failed, falling back to Noble', {
322
+ error: {
323
+ message: error instanceof Error ? error.message : String(error),
324
+ stack: error instanceof Error ? error.stack : undefined
325
+ }
326
+ });
327
+ return this.nobleProvider.encrypt(key, data, iv);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Decrypt data using AES-GCM with QuickCrypto (much faster than Noble)
333
+ */
334
+ async decrypt(key: CryptoKeyLike, data: ArrayBuffer, iv: ArrayBuffer): Promise<ArrayBuffer> {
335
+ if (!this.quickCrypto || !this.isAvailable) {
336
+ this.logger?.debug('QuickCrypto not available, falling back to Noble for decryption');
337
+ return this.nobleProvider.decrypt(key, data, iv);
338
+ }
339
+
340
+ this.logger?.debug('Decrypting data with AES-GCM using QuickCrypto', {
341
+ dataSize: data.byteLength,
342
+ ivSize: iv.byteLength
343
+ });
344
+
345
+ const secretKey = key as QuickCryptoKey;
346
+
347
+ if (secretKey.type !== 'secret') {
348
+ const error = new Error('Expected secret key');
349
+ this.logger?.error('Decryption failed - invalid key type', {
350
+ actualType: secretKey.type
351
+ });
352
+ throw error;
353
+ }
354
+
355
+ try {
356
+ const dataBytes = new Uint8Array(data);
357
+
358
+ // Extract auth tag from end of data (last 16 bytes for GCM)
359
+ const authTagLength = 16;
360
+ if (dataBytes.length < authTagLength) {
361
+ throw new Error('Data too short to contain auth tag');
362
+ }
363
+
364
+ const ciphertext = dataBytes.slice(0, -authTagLength);
365
+ const authTag = dataBytes.slice(-authTagLength);
366
+
367
+ const decipher = this.quickCrypto.createDecipheriv('aes-256-gcm', secretKey.data, new Uint8Array(iv));
368
+
369
+ // Set auth tag for verification
370
+ decipher.setAuthTag?.(authTag);
371
+
372
+ const decrypted = decipher.update(ciphertext);
373
+ const final = decipher.final();
374
+
375
+ // Combine decrypted chunks
376
+ const result = new Uint8Array(decrypted.length + final.length);
377
+ result.set(decrypted, 0);
378
+ result.set(final, decrypted.length);
379
+
380
+ this.logger?.debug('Decryption completed using QuickCrypto', {
381
+ plaintextSize: result.byteLength
382
+ });
383
+
384
+ return result.buffer.slice(result.byteOffset, result.byteOffset + result.byteLength);
385
+ } catch (error) {
386
+ this.logger?.error('QuickCrypto decryption failed, falling back to Noble', {
387
+ error: {
388
+ message: error instanceof Error ? error.message : String(error),
389
+ stack: error instanceof Error ? error.stack : undefined
390
+ }
391
+ });
392
+ return this.nobleProvider.decrypt(key, data, iv);
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Generate HMAC-SHA256 using QuickCrypto (faster than Noble)
398
+ */
399
+ async generateHMAC(key: CryptoKeyLike, data: ArrayBuffer): Promise<ArrayBuffer> {
400
+ if (!this.quickCrypto || !this.isAvailable) {
401
+ this.logger?.debug('QuickCrypto not available, falling back to Noble for HMAC');
402
+ return this.nobleProvider.generateHMAC(key, data);
403
+ }
404
+
405
+ const secretKey = key as QuickCryptoKey;
406
+
407
+ if (secretKey.type !== 'secret') {
408
+ throw new Error('Expected secret key');
409
+ }
410
+
411
+ try {
412
+ const hmac = this.quickCrypto.createHmac('sha256', secretKey.data);
413
+ hmac.update(new Uint8Array(data));
414
+ const mac = hmac.digest();
415
+
416
+ return mac.buffer.slice(mac.byteOffset, mac.byteOffset + mac.byteLength);
417
+ } catch (error) {
418
+ this.logger?.error('QuickCrypto HMAC failed, falling back to Noble', {
419
+ error: {
420
+ message: error instanceof Error ? error.message : String(error)
421
+ }
422
+ });
423
+ return this.nobleProvider.generateHMAC(key, data);
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Verify HMAC-SHA256 using QuickCrypto (faster than Noble)
429
+ */
430
+ async verifyHMAC(key: CryptoKeyLike, data: ArrayBuffer, mac: ArrayBuffer): Promise<boolean> {
431
+ if (!this.quickCrypto || !this.isAvailable) {
432
+ this.logger?.debug('QuickCrypto not available, falling back to Noble for HMAC verification');
433
+ return this.nobleProvider.verifyHMAC(key, data, mac);
434
+ }
435
+
436
+ try {
437
+ const expectedMac = await this.generateHMAC(key, data);
438
+ const providedMac = new Uint8Array(mac);
439
+ const expectedMacBytes = new Uint8Array(expectedMac);
440
+
441
+ // Constant-time comparison
442
+ if (expectedMacBytes.length !== providedMac.length) {
443
+ return false;
444
+ }
445
+
446
+ let result = 0;
447
+ for (let i = 0; i < expectedMacBytes.length; i++) {
448
+ result |= expectedMacBytes[i] ^ providedMac[i];
449
+ }
450
+
451
+ return result === 0;
452
+ } catch (error) {
453
+ this.logger?.error('QuickCrypto HMAC verification failed, falling back to Noble', {
454
+ error: {
455
+ message: error instanceof Error ? error.message : String(error)
456
+ }
457
+ });
458
+ return this.nobleProvider.verifyHMAC(key, data, mac);
459
+ }
460
+ }
461
+ }
462
+
463
+ /**
464
+ * Self-register QuickCrypto provider on import
465
+ * This allows the provider to be available when explicitly imported
466
+ *
467
+ * Note: QuickCrypto depends on Noble for ECDH operations, so we auto-register
468
+ * Noble as well to ensure all required functionality is available.
469
+ */
470
+ // Auto-register noble as a dependency (QuickCrypto delegates ECDH to Noble)
471
+ import './noble-provider';
472
+
473
+ registerCryptoProvider('quickcrypto', (logger) => new QuickCryptoProvider(logger));
474
+
475
+ // TypeScript module augmentation to track this provider is available
476
+ declare global {
477
+ // eslint-disable-next-line @typescript-eslint/no-namespace
478
+ namespace BananaLink {
479
+ interface RegisteredCryptoProviders {
480
+ quickcrypto: true;
481
+ }
482
+ }
483
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Global registry for crypto providers
3
+ *
4
+ * This registry allows providers to self-register when imported,
5
+ * enabling explicit import patterns while maintaining the factory pattern
6
+ * internally within BananaLink and BananaWallet classes.
7
+ */
8
+
9
+ import type { CryptoProvider, CryptoProviderType } from '../../types/crypto-provider';
10
+ import type { Logger } from '@bananalink-sdk/logger';
11
+
12
+ /**
13
+ * Factory function type for creating crypto provider instances
14
+ */
15
+ export type CryptoProviderFactory = (logger?: Logger) => CryptoProvider;
16
+
17
+ /**
18
+ * Global registry storing available crypto providers
19
+ */
20
+ const cryptoProviders = new Map<CryptoProviderType, CryptoProviderFactory>();
21
+
22
+ /**
23
+ * Register a crypto provider factory
24
+ * Called automatically when a provider module is imported
25
+ *
26
+ * This function is idempotent - calling it multiple times with the same type
27
+ * will only register the provider once.
28
+ *
29
+ * @param type - The provider type identifier
30
+ * @param factory - Factory function that creates provider instances
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * // In noble-provider.ts
35
+ * registerCryptoProvider('noble', (logger) => new NobleCryptoProvider(logger));
36
+ * ```
37
+ */
38
+ export function registerCryptoProvider(
39
+ type: CryptoProviderType,
40
+ factory: CryptoProviderFactory
41
+ ): void {
42
+ // Skip if already registered (idempotent)
43
+ if (cryptoProviders.has(type)) {
44
+ return;
45
+ }
46
+ cryptoProviders.set(type, factory);
47
+ }
48
+
49
+ /**
50
+ * Get a registered crypto provider factory
51
+ *
52
+ * @param type - The provider type to retrieve
53
+ * @returns The provider factory function
54
+ * @throws Error if the provider is not registered
55
+ */
56
+ export function getCryptoProviderFactory(type: CryptoProviderType): CryptoProviderFactory {
57
+ const factory = cryptoProviders.get(type);
58
+
59
+ if (!factory) {
60
+ const availableProviders = Array.from(cryptoProviders.keys()).join(', ');
61
+ const importHint = getImportHint(type);
62
+
63
+ throw new Error(
64
+ `Crypto provider '${type}' is not registered. ${importHint}\n` +
65
+ `Available providers: ${availableProviders || 'none'}\n` +
66
+ `Make sure to import the provider before using it.`
67
+ );
68
+ }
69
+
70
+ return factory;
71
+ }
72
+
73
+ /**
74
+ * Check if a crypto provider is registered
75
+ *
76
+ * @param type - The provider type to check
77
+ * @returns True if the provider is registered
78
+ */
79
+ export function isCryptoProviderRegistered(type: CryptoProviderType): boolean {
80
+ return cryptoProviders.has(type);
81
+ }
82
+
83
+ /**
84
+ * Get all registered crypto provider types
85
+ *
86
+ * @returns Array of registered provider types
87
+ */
88
+ export function getRegisteredCryptoProviders(): CryptoProviderType[] {
89
+ return Array.from(cryptoProviders.keys());
90
+ }
91
+
92
+ /**
93
+ * Clear all registered providers (useful for testing)
94
+ */
95
+ export function clearCryptoProviderRegistry(): void {
96
+ cryptoProviders.clear();
97
+ }
98
+
99
+ /**
100
+ * Get import hint for a specific provider type
101
+ */
102
+ function getImportHint(type: CryptoProviderType): string {
103
+ switch (type) {
104
+ case 'noble':
105
+ return "Did you forget to import '@bananalink-sdk/protocol/crypto/provider/noble'?";
106
+ case 'quickcrypto':
107
+ return "Did you forget to import '@bananalink-sdk/protocol/crypto/provider/quickcrypto'?";
108
+ case 'webcrypto':
109
+ return "Did you forget to import '@bananalink-sdk/protocol/crypto/provider/webcrypto'?";
110
+ case 'node':
111
+ return "Did you forget to import '@bananalink-sdk/protocol/crypto/provider/node'?";
112
+ default:
113
+ return `Did you forget to import the '${String(type)}' provider?`;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Type augmentation for registered providers
119
+ * This allows TypeScript to track which providers are available
120
+ */
121
+ declare global {
122
+ // eslint-disable-next-line @typescript-eslint/no-namespace
123
+ namespace BananaLink {
124
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
125
+ interface RegisteredCryptoProviders {
126
+ // This will be augmented by each provider module
127
+ }
128
+ }
129
+ }