@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,204 @@
1
+ import type { Logger } from '@bananalink-sdk/logger';
2
+ import type { CryptoProvider, CryptoProviderType } from '../../types/crypto-provider';
3
+ import { CryptoProviderUnavailableError } from '../errors';
4
+ import { detectPlatform } from '../utils';
5
+ import {
6
+ getCryptoProviderFactory,
7
+ getRegisteredCryptoProviders
8
+ } from './registry';
9
+
10
+ /**
11
+ * Generate context-appropriate recommendations for unavailable providers
12
+ */
13
+ function generateRecommendations(
14
+ requestedProvider: CryptoProviderType,
15
+ platform: ReturnType<typeof detectPlatform>
16
+ ): string[] {
17
+ const recommendations: string[] = [];
18
+
19
+ // Provider-specific recommendations
20
+ if (requestedProvider === 'webcrypto') {
21
+ recommendations.push("WebCrypto requires a browser or Node.js 15+ environment");
22
+ if (platform.isNode) {
23
+ recommendations.push("Try 'node' provider for Node.js");
24
+ }
25
+ } else if (requestedProvider === 'node') {
26
+ recommendations.push("Node crypto requires Node.js environment");
27
+ if (platform.isBrowser) {
28
+ recommendations.push("Try 'webcrypto' provider for browsers");
29
+ }
30
+ } else if (requestedProvider === 'quickcrypto') {
31
+ recommendations.push("QuickCrypto requires React Native environment");
32
+ if (!platform.isReactNative) {
33
+ if (platform.isNode) {
34
+ recommendations.push("Try 'node' provider for Node.js");
35
+ } else if (platform.isBrowser) {
36
+ recommendations.push("Try 'webcrypto' provider for browsers");
37
+ }
38
+ }
39
+ }
40
+
41
+ // Universal fallback
42
+ recommendations.push("'noble' provider works in all environments as a fallback");
43
+ recommendations.push(`Import the provider: import '@bananalink-sdk/protocol/crypto/provider/${requestedProvider}'`);
44
+
45
+ return recommendations;
46
+ }
47
+
48
+ /**
49
+ * Create a crypto provider based on type
50
+ *
51
+ * This factory function uses the global registry to instantiate providers.
52
+ * Providers must be explicitly imported before they can be used.
53
+ *
54
+ * @param preferredProvider - Optional preferred provider type
55
+ * @param logger - Optional logger for debug output
56
+ * @param strict - If true, throw error if preferred provider unavailable
57
+ * @returns The created crypto provider instance
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * // Import providers to make them available
62
+ * import '@bananalink-sdk/protocol/crypto/provider/noble';
63
+ * import '@bananalink-sdk/protocol/crypto/provider/webcrypto';
64
+ *
65
+ * // Create provider
66
+ * const provider = createCryptoProvider('noble');
67
+ * ```
68
+ */
69
+ export function createCryptoProvider(
70
+ preferredProvider?: CryptoProviderType,
71
+ logger?: Logger,
72
+ strict?: boolean
73
+ ): CryptoProvider {
74
+ const registeredProviders = getRegisteredCryptoProviders();
75
+
76
+ logger?.debug('Creating crypto provider', {
77
+ preferredProvider,
78
+ strict,
79
+ registeredProviders
80
+ });
81
+
82
+ // Determine which provider to use
83
+ let providerType: CryptoProviderType | undefined = preferredProvider;
84
+
85
+ // Strict validation: If no provider specified, require exactly one registered
86
+ if (!providerType) {
87
+ if (registeredProviders.length === 0) {
88
+ throw new CryptoProviderUnavailableError(
89
+ 'No crypto providers are registered. Did you forget to import a provider?',
90
+ {
91
+ availableProviders: [],
92
+ recommendations: [
93
+ "Import at least one crypto provider, e.g.: import '@bananalink-sdk/protocol/crypto/provider/noble'",
94
+ "Noble provider is recommended as a universal fallback"
95
+ ]
96
+ }
97
+ );
98
+ } else if (registeredProviders.length === 1) {
99
+ // Auto-select the single registered provider
100
+ providerType = registeredProviders[0];
101
+ logger?.debug(`Auto-selected '${providerType}' provider (only one registered)`);
102
+ } else {
103
+ // Multiple registered - require explicit selection
104
+ throw new CryptoProviderUnavailableError(
105
+ `Multiple crypto providers registered: ${registeredProviders.join(', ')}. ` +
106
+ `Either import only one provider OR explicitly set cryptoProvider.type. ` +
107
+ `Example: cryptoProvider: { type: '${registeredProviders[0]}' }`,
108
+ {
109
+ availableProviders: registeredProviders,
110
+ recommendations: [
111
+ `Import only one crypto provider, or specify which one to use`,
112
+ `Registered providers: ${registeredProviders.join(', ')}`,
113
+ `Set cryptoProvider.type to one of the registered providers`
114
+ ]
115
+ }
116
+ );
117
+ }
118
+ }
119
+
120
+ // Try to create the provider
121
+ try {
122
+ const factory = getCryptoProviderFactory(providerType);
123
+ const provider = factory(logger);
124
+
125
+ logger?.info(`Using ${provider.name} crypto provider`, {
126
+ providerType,
127
+ isAvailable: provider.isAvailable
128
+ });
129
+
130
+ return provider;
131
+ } catch (error) {
132
+ // If strict mode AND user explicitly requested a specific provider, throw immediately
133
+ const userRequestedSpecificProvider = preferredProvider !== undefined;
134
+ if (strict && userRequestedSpecificProvider) {
135
+ // If already a CryptoProviderUnavailableError, re-throw as-is
136
+ if (error instanceof CryptoProviderUnavailableError) {
137
+ throw error;
138
+ }
139
+
140
+ // Wrap generic errors with full context
141
+ const platform = detectPlatform();
142
+ const availableProviders = getRegisteredCryptoProviders();
143
+ const recommendations = generateRecommendations(providerType, platform);
144
+
145
+ throw new CryptoProviderUnavailableError(
146
+ `Strict mode: Crypto provider '${providerType}' failed to initialize: ${error instanceof Error ? error.message : String(error)}`,
147
+ {
148
+ requestedProvider: providerType,
149
+ availableProviders,
150
+ platform,
151
+ recommendations,
152
+ }
153
+ );
154
+ }
155
+
156
+ // Try fallback to any available provider
157
+ logger?.warn(`Failed to create '${providerType}' provider, trying fallback`, {
158
+ error: error instanceof Error ? { message: error.message } : { message: String(error) }
159
+ });
160
+
161
+ // Try each registered provider as fallback
162
+ for (const fallbackType of registeredProviders) {
163
+ if (fallbackType === providerType) continue; // Skip the one that failed
164
+
165
+ try {
166
+ const factory = getCryptoProviderFactory(fallbackType);
167
+ const provider = factory(logger);
168
+
169
+ logger?.info(`Using ${provider.name} crypto provider (fallback)`, {
170
+ originalType: providerType,
171
+ fallbackType
172
+ });
173
+
174
+ return provider;
175
+ } catch (fallbackError) {
176
+ logger?.debug(`Fallback to '${fallbackType}' failed`, {
177
+ error: fallbackError instanceof Error ? { message: fallbackError.message } : { message: String(fallbackError) }
178
+ });
179
+ }
180
+ }
181
+
182
+ // If we get here, no providers worked
183
+ const platform = detectPlatform();
184
+ const recommendations = generateRecommendations(providerType, platform);
185
+
186
+ throw new CryptoProviderUnavailableError(
187
+ `Failed to create any crypto provider. Requested: '${providerType}'`,
188
+ {
189
+ requestedProvider: providerType,
190
+ availableProviders: registeredProviders,
191
+ platform,
192
+ recommendations,
193
+ }
194
+ );
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Export registry functions for advanced usage
200
+ */
201
+ export {
202
+ isCryptoProviderRegistered,
203
+ getRegisteredCryptoProviders
204
+ } from './registry';
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Crypto providers for environment-agnostic cryptographic operations
3
+ */
4
+
5
+ // Types
6
+ export type {
7
+ CryptoProvider,
8
+ CryptoKeyLike,
9
+ ProviderKeyPair,
10
+ ProviderDetectionResult,
11
+ CryptoProviderType,
12
+ PlatformDetectionResult
13
+ } from '../../types/crypto-provider';
14
+
15
+ // Compliance types
16
+ export type {
17
+ ComplianceEventType,
18
+ ComplianceAuditEvent,
19
+ ComplianceAuditor,
20
+ KeyUsageRestrictions,
21
+ ComplianceConfig
22
+ } from './compliance-provider';
23
+
24
+ // Factory functions
25
+ export {
26
+ createCryptoProvider,
27
+ isCryptoProviderRegistered,
28
+ getRegisteredCryptoProviders,
29
+ } from './factory';
30
+
31
+ // Registry functions (for advanced usage)
32
+ export {
33
+ registerCryptoProvider,
34
+ getCryptoProviderFactory,
35
+ clearCryptoProviderRegistry,
36
+ type CryptoProviderFactory
37
+ } from './registry';
38
+
39
+ // Note: Provider implementations should be imported from specific subpaths for explicit loading:
40
+ // - './webcrypto-provider' for WebCryptoProvider
41
+ // - './node-provider' for NodeCryptoProvider
42
+ // - './noble-provider' for NobleCryptoProvider
43
+ // - './quickcrypto-provider' for QuickCryptoProvider
44
+ // - './compliance-provider' for ComplianceCryptoProvider and DefaultComplianceAuditor
@@ -0,0 +1,392 @@
1
+ import { p256 } from '@noble/curves/nist.js';
2
+ import { gcm } from '@noble/ciphers/aes.js';
3
+ import { randomBytes as nobleRandomBytes } from '@noble/ciphers/utils.js';
4
+ import { hkdf } from '@noble/hashes/hkdf.js';
5
+ import { hmac } from '@noble/hashes/hmac.js';
6
+ import { sha256 } from '@noble/hashes/sha2.js';
7
+ import type { Logger } from '@bananalink-sdk/logger';
8
+ import type { CryptoProvider, CryptoKeyLike, ProviderKeyPair } from '../../types/crypto-provider';
9
+ import { registerCryptoProvider } from './registry';
10
+
11
+ /**
12
+ * Noble library implementation of CryptoKeyLike
13
+ */
14
+ class NobleKey implements CryptoKeyLike {
15
+ constructor(
16
+ public readonly data: Uint8Array,
17
+ public readonly type: 'public' | 'private' | 'secret',
18
+ public readonly algorithm: string,
19
+ public readonly extractable: boolean = true,
20
+ public readonly usages: readonly string[] = []
21
+ ) {}
22
+ }
23
+
24
+ /**
25
+ * Noble cryptography implementation of CryptoProvider
26
+ * Pure JavaScript implementation that works in all environments
27
+ */
28
+ export class NobleCryptoProvider implements CryptoProvider {
29
+ public readonly name = 'Noble';
30
+ public readonly isAvailable = true; // Noble libraries work everywhere
31
+ private readonly logger?: Logger;
32
+
33
+ constructor(logger?: Logger) {
34
+ this.logger = logger?.child({ component: 'NobleCryptoProvider' });
35
+ }
36
+
37
+ /**
38
+ * Generate ECDH P-256 key pair
39
+ */
40
+ generateKeyPair(): Promise<ProviderKeyPair> {
41
+ this.logger?.debug('Generating ECDH P-256 key pair using Noble');
42
+
43
+ try {
44
+ const privateKeyBytes = p256.utils.randomSecretKey();
45
+ // Generate uncompressed public key to match WebCrypto format (65 bytes)
46
+ const publicKeyUncompressed = p256.getPublicKey(privateKeyBytes, false); // false = uncompressed
47
+
48
+ this.logger?.debug('Key pair generation completed', {
49
+ publicKeyLength: publicKeyUncompressed.length
50
+ });
51
+
52
+ return Promise.resolve({
53
+ privateKey: new NobleKey(privateKeyBytes, 'private', 'ECDH-P256', true, ['deriveKey']),
54
+ publicKey: new NobleKey(publicKeyUncompressed, 'public', 'ECDH-P256', true, []),
55
+ });
56
+ } catch (error) {
57
+ this.logger?.error('Key pair generation failed', {
58
+ error: {
59
+ message: error instanceof Error ? error.message : String(error),
60
+ stack: error instanceof Error ? error.stack : undefined
61
+ }
62
+ });
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Export public key to ArrayBuffer (raw format)
69
+ */
70
+ exportPublicKey(publicKey: CryptoKeyLike): Promise<ArrayBuffer> {
71
+ const nobleKey = publicKey as NobleKey;
72
+ if (nobleKey.type !== 'public') {
73
+ throw new Error('Expected public key');
74
+ }
75
+ return Promise.resolve(nobleKey.data.buffer.slice(nobleKey.data.byteOffset, nobleKey.data.byteOffset + nobleKey.data.byteLength));
76
+ }
77
+
78
+ /**
79
+ * Export private key to ArrayBuffer (raw format)
80
+ */
81
+ exportPrivateKey(privateKey: CryptoKeyLike): Promise<ArrayBuffer> {
82
+ const nobleKey = privateKey as NobleKey;
83
+ if (nobleKey.type !== 'private') {
84
+ throw new Error('Expected private key');
85
+ }
86
+ return Promise.resolve(nobleKey.data.buffer.slice(nobleKey.data.byteOffset, nobleKey.data.byteOffset + nobleKey.data.byteLength));
87
+ }
88
+
89
+ /**
90
+ * Import public key from ArrayBuffer (raw format)
91
+ */
92
+ importPublicKey(keyData: ArrayBuffer): Promise<CryptoKeyLike> {
93
+ const keyBytes = new Uint8Array(keyData);
94
+
95
+ // Debug logging
96
+ this.logger?.debug('importPublicKey called', {
97
+ keyLength: keyBytes.length,
98
+ keyBytesFirst20: Array.from(keyBytes.slice(0, 20)).map(b => b.toString(16).padStart(2, '0')).join(' '),
99
+ keyBytesHex: Array.from(keyBytes).map(b => b.toString(16).padStart(2, '0')).join('')
100
+ });
101
+
102
+ // Validate public key using Noble's validation
103
+ try {
104
+ let processedKeyBytes: Uint8Array;
105
+
106
+ // Handle different key formats
107
+ if (keyBytes.length === 65) {
108
+ // Already in uncompressed format (0x04 + 32 + 32)
109
+ this.logger?.debug('Processing 65-byte key', {
110
+ firstByte: `0x${keyBytes[0].toString(16)}`
111
+ });
112
+ if (keyBytes[0] !== 0x04) {
113
+ throw new Error(`Expected uncompressed key (0x04 prefix), got 0x${keyBytes[0].toString(16)}`);
114
+ }
115
+ processedKeyBytes = keyBytes;
116
+ } else if (keyBytes.length === 64) {
117
+ // Raw format (32 + 32) - needs 0x04 prefix for Noble
118
+ // This is the WebCrypto export format
119
+ this.logger?.debug('Processing 64-byte key', {
120
+ action: 'adding 0x04 prefix'
121
+ });
122
+ processedKeyBytes = new Uint8Array(65);
123
+ processedKeyBytes[0] = 0x04; // Uncompressed point indicator
124
+ processedKeyBytes.set(keyBytes, 1); // Copy the 64 bytes after the prefix
125
+ } else {
126
+ throw new Error(`Invalid key length: expected 64 or 65 bytes, got ${keyBytes.length} bytes`);
127
+ }
128
+
129
+ this.logger?.debug('Key processing completed', {
130
+ processedLength: processedKeyBytes.length,
131
+ processedFirst20: Array.from(processedKeyBytes.slice(0, 20)).map(b => b.toString(16).padStart(2, '0')).join(' ')
132
+ });
133
+
134
+ // Validate using Noble's validation
135
+ const isValid = p256.utils.isValidPublicKey(processedKeyBytes, false); // false = allow uncompressed
136
+ this.logger?.debug('Noble key validation completed', {
137
+ isValid
138
+ });
139
+
140
+ if (!isValid) {
141
+ // Try additional debugging
142
+ this.logger?.debug('Key validation failed, trying alternative approaches', {
143
+ originalValidation: false
144
+ });
145
+
146
+ // Try with compressed format check
147
+ const isValidCompressed = p256.utils.isValidPublicKey(processedKeyBytes, true);
148
+ this.logger?.debug('Alternative validation attempted', {
149
+ compressedFormatValid: isValidCompressed
150
+ });
151
+
152
+ throw new Error('Key failed Noble validation');
153
+ }
154
+
155
+ return Promise.resolve(new NobleKey(processedKeyBytes, 'public', 'ECDH-P256', true, []));
156
+ } catch (error) {
157
+ this.logger?.error('Public key import failed', {
158
+ error: {
159
+ message: error instanceof Error ? error.message : String(error),
160
+ stack: error instanceof Error ? error.stack : undefined
161
+ }
162
+ });
163
+ throw new Error(`Invalid P-256 public key: ${String(error)}`);
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Import private key from ArrayBuffer (raw format)
169
+ */
170
+ importPrivateKey(keyData: ArrayBuffer): Promise<CryptoKeyLike> {
171
+ const keyBytes = new Uint8Array(keyData);
172
+
173
+ // Validate private key (Noble will throw if invalid)
174
+ try {
175
+ if (!p256.utils.isValidSecretKey(keyBytes)) {
176
+ throw new Error('Invalid secret key');
177
+ }
178
+ } catch (error) {
179
+ throw new Error(`Invalid P-256 private key: ${String(error)}`);
180
+ }
181
+
182
+ return Promise.resolve(new NobleKey(keyBytes, 'private', 'ECDH-P256', true, ['deriveKey']));
183
+ }
184
+
185
+ /**
186
+ * Derive shared secret from ECDH key agreement
187
+ */
188
+ deriveSharedSecret(privateKey: CryptoKeyLike, publicKey: CryptoKeyLike): Promise<CryptoKeyLike> {
189
+ this.logger?.debug('Deriving shared secret using ECDH with Noble');
190
+
191
+ const privKey = privateKey as NobleKey;
192
+ const pubKey = publicKey as NobleKey;
193
+
194
+ if (privKey.type !== 'private') {
195
+ const error = new Error('Expected private key');
196
+ this.logger?.error('Shared secret derivation failed - invalid private key type', {
197
+ actualType: privKey.type
198
+ });
199
+ throw error;
200
+ }
201
+ if (pubKey.type !== 'public') {
202
+ const error = new Error('Expected public key');
203
+ this.logger?.error('Shared secret derivation failed - invalid public key type', {
204
+ actualType: pubKey.type
205
+ });
206
+ throw error;
207
+ }
208
+
209
+ try {
210
+ // Perform ECDH key agreement - return x-coordinate only to match WebCrypto
211
+ const sharedSecret = p256.getSharedSecret(privKey.data, pubKey.data, false); // uncompressed
212
+ // Extract only the x-coordinate (32 bytes) to match WebCrypto behavior
213
+ const xCoordinate = sharedSecret.slice(1, 33); // Skip 0x04 prefix, take next 32 bytes
214
+
215
+ this.logger?.debug('Shared secret derivation completed', {
216
+ sharedSecretLength: xCoordinate.length
217
+ });
218
+
219
+ return Promise.resolve(new NobleKey(xCoordinate, 'secret', 'AES-GCM', true, ['encrypt', 'decrypt']));
220
+ } catch (error) {
221
+ this.logger?.error('Shared secret derivation failed', {
222
+ error: {
223
+ message: error instanceof Error ? error.message : String(error),
224
+ stack: error instanceof Error ? error.stack : undefined
225
+ }
226
+ });
227
+ throw error;
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Derive AES-GCM key from shared secret using HKDF
233
+ */
234
+ deriveEncryptionKey(sharedSecret: CryptoKeyLike, salt: ArrayBuffer, info: ArrayBuffer): Promise<CryptoKeyLike> {
235
+ const secretKey = sharedSecret as NobleKey;
236
+
237
+ if (secretKey.type !== 'secret') {
238
+ throw new Error('Expected secret key');
239
+ }
240
+
241
+ // Perform HKDF with SHA-256
242
+ const derivedKey = hkdf(sha256, secretKey.data, new Uint8Array(salt), new Uint8Array(info), 32); // 32 bytes = 256 bits
243
+
244
+ return Promise.resolve(new NobleKey(derivedKey, 'secret', 'AES-GCM', true, ['encrypt', 'decrypt']));
245
+ }
246
+
247
+ /**
248
+ * Generate random bytes
249
+ */
250
+ randomBytes(length: number): ArrayBuffer {
251
+ const bytes = nobleRandomBytes(length);
252
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
253
+ }
254
+
255
+ /**
256
+ * Encrypt data using AES-GCM
257
+ */
258
+ encrypt(key: CryptoKeyLike, data: ArrayBuffer, iv: ArrayBuffer): Promise<ArrayBuffer> {
259
+ this.logger?.debug('Encrypting data with AES-GCM using Noble', {
260
+ dataSize: data.byteLength,
261
+ ivSize: iv.byteLength
262
+ });
263
+
264
+ const secretKey = key as NobleKey;
265
+
266
+ if (secretKey.type !== 'secret') {
267
+ const error = new Error('Expected secret key');
268
+ this.logger?.error('Encryption failed - invalid key type', {
269
+ actualType: secretKey.type
270
+ });
271
+ throw error;
272
+ }
273
+
274
+ try {
275
+ const aesGcm = gcm(secretKey.data, new Uint8Array(iv));
276
+ const ciphertext = aesGcm.encrypt(new Uint8Array(data));
277
+
278
+ this.logger?.debug('Encryption completed', {
279
+ ciphertextSize: ciphertext.byteLength
280
+ });
281
+
282
+ return Promise.resolve(ciphertext.buffer.slice(ciphertext.byteOffset, ciphertext.byteOffset + ciphertext.byteLength));
283
+ } catch (error) {
284
+ this.logger?.error('Encryption failed', {
285
+ error: {
286
+ message: error instanceof Error ? error.message : String(error),
287
+ stack: error instanceof Error ? error.stack : undefined
288
+ }
289
+ });
290
+ throw error;
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Decrypt data using AES-GCM
296
+ */
297
+ decrypt(key: CryptoKeyLike, data: ArrayBuffer, iv: ArrayBuffer): Promise<ArrayBuffer> {
298
+ this.logger?.debug('Decrypting data with AES-GCM using Noble', {
299
+ dataSize: data.byteLength,
300
+ ivSize: iv.byteLength
301
+ });
302
+
303
+ const secretKey = key as NobleKey;
304
+
305
+ if (secretKey.type !== 'secret') {
306
+ const error = new Error('Expected secret key');
307
+ this.logger?.error('Decryption failed - invalid key type', {
308
+ actualType: secretKey.type
309
+ });
310
+ throw error;
311
+ }
312
+
313
+ try {
314
+ const aesGcm = gcm(secretKey.data, new Uint8Array(iv));
315
+ const plaintext = aesGcm.decrypt(new Uint8Array(data));
316
+
317
+ this.logger?.debug('Decryption completed', {
318
+ plaintextSize: plaintext.byteLength
319
+ });
320
+
321
+ return Promise.resolve(plaintext.buffer.slice(plaintext.byteOffset, plaintext.byteOffset + plaintext.byteLength));
322
+ } catch (error) {
323
+ this.logger?.error('Decryption failed', {
324
+ error: {
325
+ message: error instanceof Error ? error.message : String(error),
326
+ stack: error instanceof Error ? error.stack : undefined
327
+ }
328
+ });
329
+ throw error;
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Generate HMAC-SHA256
335
+ */
336
+ generateHMAC(key: CryptoKeyLike, data: ArrayBuffer): Promise<ArrayBuffer> {
337
+ const secretKey = key as NobleKey;
338
+
339
+ if (secretKey.type !== 'secret') {
340
+ throw new Error('Expected secret key');
341
+ }
342
+
343
+ const mac = hmac(sha256, secretKey.data, new Uint8Array(data));
344
+ return Promise.resolve(mac.buffer.slice(mac.byteOffset, mac.byteOffset + mac.byteLength));
345
+ }
346
+
347
+ /**
348
+ * Verify HMAC-SHA256
349
+ */
350
+ verifyHMAC(key: CryptoKeyLike, data: ArrayBuffer, mac: ArrayBuffer): Promise<boolean> {
351
+ const secretKey = key as NobleKey;
352
+
353
+ if (secretKey.type !== 'secret') {
354
+ throw new Error('Expected secret key');
355
+ }
356
+
357
+ try {
358
+ const expectedMac = hmac(sha256, secretKey.data, new Uint8Array(data));
359
+ const providedMac = new Uint8Array(mac);
360
+
361
+ // Constant-time comparison
362
+ if (expectedMac.length !== providedMac.length) {
363
+ return Promise.resolve(false);
364
+ }
365
+
366
+ let result = 0;
367
+ for (let i = 0; i < expectedMac.length; i++) {
368
+ result |= expectedMac[i] ^ providedMac[i];
369
+ }
370
+
371
+ return Promise.resolve(result === 0);
372
+ } catch {
373
+ return Promise.resolve(false);
374
+ }
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Self-register Noble provider on import
380
+ * This allows the provider to be available when explicitly imported
381
+ */
382
+ registerCryptoProvider('noble', (logger) => new NobleCryptoProvider(logger));
383
+
384
+ // TypeScript module augmentation to track this provider is available
385
+ declare global {
386
+ // eslint-disable-next-line @typescript-eslint/no-namespace
387
+ namespace BananaLink {
388
+ interface RegisteredCryptoProviders {
389
+ noble: true;
390
+ }
391
+ }
392
+ }