@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.
- package/README.md +604 -0
- package/dist/chunk-32OWUOZ3.js +308 -0
- package/dist/chunk-32OWUOZ3.js.map +1 -0
- package/dist/chunk-65HNHRJK.cjs +123 -0
- package/dist/chunk-65HNHRJK.cjs.map +1 -0
- package/dist/chunk-7KYDLL3B.js +480 -0
- package/dist/chunk-7KYDLL3B.js.map +1 -0
- package/dist/chunk-A6FLEJ7R.cjs +62 -0
- package/dist/chunk-A6FLEJ7R.cjs.map +1 -0
- package/dist/chunk-CUJK7ZTS.js +217 -0
- package/dist/chunk-CUJK7ZTS.js.map +1 -0
- package/dist/chunk-GI3BUPIH.cjs +236 -0
- package/dist/chunk-GI3BUPIH.cjs.map +1 -0
- package/dist/chunk-JXHV66Q4.js +106 -0
- package/dist/chunk-JXHV66Q4.js.map +1 -0
- package/dist/chunk-KNGZKGRS.cjs +552 -0
- package/dist/chunk-KNGZKGRS.cjs.map +1 -0
- package/dist/chunk-LELPCIE7.js +840 -0
- package/dist/chunk-LELPCIE7.js.map +1 -0
- package/dist/chunk-MCZG7QEM.cjs +310 -0
- package/dist/chunk-MCZG7QEM.cjs.map +1 -0
- package/dist/chunk-TCVKC227.js +56 -0
- package/dist/chunk-TCVKC227.js.map +1 -0
- package/dist/chunk-VXLUSU5B.cjs +856 -0
- package/dist/chunk-VXLUSU5B.cjs.map +1 -0
- package/dist/chunk-WCQVDF3K.js +12 -0
- package/dist/chunk-WCQVDF3K.js.map +1 -0
- package/dist/chunk-WGEGR3DF.cjs +15 -0
- package/dist/chunk-WGEGR3DF.cjs.map +1 -0
- package/dist/client-session-claim-3QF3noOr.d.ts +197 -0
- package/dist/client-session-claim-C4lUik3b.d.cts +197 -0
- package/dist/core-DMhuNfoz.d.cts +62 -0
- package/dist/core-DMhuNfoz.d.ts +62 -0
- package/dist/crypto/providers/noble-provider.cjs +14 -0
- package/dist/crypto/providers/noble-provider.cjs.map +1 -0
- package/dist/crypto/providers/noble-provider.d.cts +30 -0
- package/dist/crypto/providers/noble-provider.d.ts +30 -0
- package/dist/crypto/providers/noble-provider.js +5 -0
- package/dist/crypto/providers/noble-provider.js.map +1 -0
- package/dist/crypto/providers/node-provider.cjs +308 -0
- package/dist/crypto/providers/node-provider.cjs.map +1 -0
- package/dist/crypto/providers/node-provider.d.cts +32 -0
- package/dist/crypto/providers/node-provider.d.ts +32 -0
- package/dist/crypto/providers/node-provider.js +306 -0
- package/dist/crypto/providers/node-provider.js.map +1 -0
- package/dist/crypto/providers/quickcrypto-provider.cjs +339 -0
- package/dist/crypto/providers/quickcrypto-provider.cjs.map +1 -0
- package/dist/crypto/providers/quickcrypto-provider.d.cts +34 -0
- package/dist/crypto/providers/quickcrypto-provider.d.ts +34 -0
- package/dist/crypto/providers/quickcrypto-provider.js +337 -0
- package/dist/crypto/providers/quickcrypto-provider.js.map +1 -0
- package/dist/crypto/providers/webcrypto-provider.cjs +310 -0
- package/dist/crypto/providers/webcrypto-provider.cjs.map +1 -0
- package/dist/crypto/providers/webcrypto-provider.d.cts +30 -0
- package/dist/crypto/providers/webcrypto-provider.d.ts +30 -0
- package/dist/crypto/providers/webcrypto-provider.js +308 -0
- package/dist/crypto/providers/webcrypto-provider.js.map +1 -0
- package/dist/crypto-BUS06Qz-.d.cts +40 -0
- package/dist/crypto-BUS06Qz-.d.ts +40 -0
- package/dist/crypto-export.cjs +790 -0
- package/dist/crypto-export.cjs.map +1 -0
- package/dist/crypto-export.d.cts +257 -0
- package/dist/crypto-export.d.ts +257 -0
- package/dist/crypto-export.js +709 -0
- package/dist/crypto-export.js.map +1 -0
- package/dist/crypto-provider-deYoVIxi.d.cts +36 -0
- package/dist/crypto-provider-deYoVIxi.d.ts +36 -0
- package/dist/index.cjs +615 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +379 -0
- package/dist/index.d.ts +379 -0
- package/dist/index.js +504 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas-export.cjs +294 -0
- package/dist/schemas-export.cjs.map +1 -0
- package/dist/schemas-export.d.cts +1598 -0
- package/dist/schemas-export.d.ts +1598 -0
- package/dist/schemas-export.js +5 -0
- package/dist/schemas-export.js.map +1 -0
- package/dist/siwe-export.cjs +237 -0
- package/dist/siwe-export.cjs.map +1 -0
- package/dist/siwe-export.d.cts +27 -0
- package/dist/siwe-export.d.ts +27 -0
- package/dist/siwe-export.js +228 -0
- package/dist/siwe-export.js.map +1 -0
- package/dist/testing.cjs +54 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +20 -0
- package/dist/testing.d.ts +20 -0
- package/dist/testing.js +51 -0
- package/dist/testing.js.map +1 -0
- package/dist/validation-export.cjs +359 -0
- package/dist/validation-export.cjs.map +1 -0
- package/dist/validation-export.d.cts +3 -0
- package/dist/validation-export.d.ts +3 -0
- package/dist/validation-export.js +6 -0
- package/dist/validation-export.js.map +1 -0
- package/dist/validators-export.cjs +73 -0
- package/dist/validators-export.cjs.map +1 -0
- package/dist/validators-export.d.cts +37 -0
- package/dist/validators-export.d.ts +37 -0
- package/dist/validators-export.js +4 -0
- package/dist/validators-export.js.map +1 -0
- package/package.json +140 -0
- package/src/constants/index.ts +205 -0
- package/src/crypto/context.ts +228 -0
- package/src/crypto/diagnostics.ts +772 -0
- package/src/crypto/errors.ts +114 -0
- package/src/crypto/index.ts +89 -0
- package/src/crypto/payload-handler.ts +102 -0
- package/src/crypto/providers/compliance-provider.ts +579 -0
- package/src/crypto/providers/factory.ts +204 -0
- package/src/crypto/providers/index.ts +44 -0
- package/src/crypto/providers/noble-provider.ts +392 -0
- package/src/crypto/providers/node-provider.ts +433 -0
- package/src/crypto/providers/quickcrypto-provider.ts +483 -0
- package/src/crypto/providers/registry.ts +129 -0
- package/src/crypto/providers/webcrypto-provider.ts +364 -0
- package/src/crypto/session-security.ts +185 -0
- package/src/crypto/types.ts +93 -0
- package/src/crypto/utils.ts +190 -0
- package/src/crypto-export.ts +21 -0
- package/src/index.ts +38 -0
- package/src/schemas/auth.ts +60 -0
- package/src/schemas/client-messages.ts +57 -0
- package/src/schemas/core.ts +144 -0
- package/src/schemas/crypto.ts +65 -0
- package/src/schemas/discovery.ts +79 -0
- package/src/schemas/index.ts +239 -0
- package/src/schemas/relay-messages.ts +45 -0
- package/src/schemas/wallet-messages.ts +177 -0
- package/src/schemas-export.ts +23 -0
- package/src/siwe-export.ts +27 -0
- package/src/testing.ts +71 -0
- package/src/types/auth.ts +60 -0
- package/src/types/client-messages.ts +84 -0
- package/src/types/core.ts +131 -0
- package/src/types/crypto-provider.ts +264 -0
- package/src/types/crypto.ts +90 -0
- package/src/types/discovery.ts +50 -0
- package/src/types/errors.ts +87 -0
- package/src/types/index.ts +197 -0
- package/src/types/post-auth-operations.ts +363 -0
- package/src/types/providers.ts +72 -0
- package/src/types/relay-messages.ts +60 -0
- package/src/types/request-lifecycle.ts +161 -0
- package/src/types/signing-operations.ts +99 -0
- package/src/types/wallet-messages.ts +251 -0
- package/src/utils/client-session-claim.ts +188 -0
- package/src/utils/index.ts +54 -0
- package/src/utils/public-keys.ts +49 -0
- package/src/utils/siwe.ts +362 -0
- package/src/utils/url-decoding.ts +126 -0
- package/src/utils/url-encoding.ts +144 -0
- package/src/utils/wallet-session-claim.ts +188 -0
- package/src/validation-export.ts +32 -0
- package/src/validators/index.ts +222 -0
- 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
|
+
}
|