@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,190 @@
1
+ import type { CryptoProvider, PlatformDetectionResult } from '../types/crypto-provider';
2
+ import { CryptoContext } from './context';
3
+
4
+ /**
5
+ * Generate cryptographically secure random bytes
6
+ *
7
+ * Uses the global CryptoContext provider (auto-initializes if needed).
8
+ * For custom provider usage, pass a provider instance.
9
+ *
10
+ * @param length - Number of bytes to generate
11
+ * @param provider - Optional custom provider (uses CryptoContext if not provided)
12
+ * @returns Cryptographically secure random bytes
13
+ *
14
+ * @example
15
+ * // Default behavior (uses CryptoContext)
16
+ * const bytes = randomBytes(32);
17
+ *
18
+ * @example
19
+ * // With custom provider
20
+ * const provider = new NobleCryptoProvider();
21
+ * const bytes = randomBytes(32, provider);
22
+ */
23
+ export function randomBytes(length: number, provider?: CryptoProvider): Uint8Array {
24
+ const activeProvider = provider || CryptoContext.getProvider();
25
+ const buffer = activeProvider.randomBytes(length);
26
+ return new Uint8Array(buffer);
27
+ }
28
+
29
+ /**
30
+ * Convert ArrayBuffer to base64 string
31
+ */
32
+ export function arrayBufferToBase64(buffer: ArrayBuffer): string {
33
+ const bytes = new Uint8Array(buffer);
34
+ let binary = '';
35
+ for (let i = 0; i < bytes.byteLength; i++) {
36
+ binary += String.fromCharCode(bytes[i]);
37
+ }
38
+ return btoa(binary);
39
+ }
40
+
41
+ /**
42
+ * Convert base64 string to ArrayBuffer
43
+ */
44
+ export function base64ToArrayBuffer(base64: string): ArrayBuffer {
45
+ // Sanitize the base64 string - remove any whitespace and invalid characters
46
+ const cleanBase64 = base64.replace(/[^A-Za-z0-9+/=]/g, '');
47
+
48
+ // Ensure proper padding
49
+ let paddedBase64 = cleanBase64;
50
+ while (paddedBase64.length % 4 !== 0) {
51
+ paddedBase64 += '=';
52
+ }
53
+
54
+ try {
55
+ const binary = atob(paddedBase64);
56
+ const bytes = new Uint8Array(binary.length);
57
+ for (let i = 0; i < binary.length; i++) {
58
+ bytes[i] = binary.charCodeAt(i);
59
+ }
60
+ return bytes.buffer;
61
+ } catch (error) {
62
+ throw new Error(`Failed to decode base64 string: ${error instanceof Error ? error.message : 'Invalid base64 format'}. Original string: ${base64.substring(0, 50)}...`);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Convert string to ArrayBuffer
68
+ */
69
+ export function stringToArrayBuffer(str: string): ArrayBuffer {
70
+ return new TextEncoder().encode(str).buffer;
71
+ }
72
+
73
+ /**
74
+ * Convert ArrayBuffer to string
75
+ */
76
+ export function arrayBufferToString(buffer: ArrayBuffer): string {
77
+ return new TextDecoder().decode(buffer);
78
+ }
79
+
80
+ /**
81
+ * Generate a cryptographically secure nonce
82
+ *
83
+ * @param length - Number of bytes to generate (default: 32)
84
+ * @param provider - Optional custom provider (uses CryptoContext if not provided)
85
+ * @returns Hex-encoded string (2 characters per byte)
86
+ *
87
+ * @example
88
+ * generateNonce() // Returns 64-char hex string (32 bytes)
89
+ * generateNonce(16) // Returns 32-char hex string (16 bytes)
90
+ *
91
+ * @example
92
+ * // With custom provider
93
+ * const provider = new NobleCryptoProvider();
94
+ * const nonce = generateNonce(32, provider);
95
+ */
96
+ export function generateNonce(length: number = 32, provider?: CryptoProvider): string {
97
+ const bytes = randomBytes(length, provider);
98
+ return Array.from(bytes, (byte: number) => byte.toString(16).padStart(2, '0')).join('');
99
+ }
100
+
101
+ /**
102
+ * Generate a RFC 4122 version 4 UUID (Universally Unique Identifier)
103
+ *
104
+ * Uses cryptographically secure random bytes from the configured CryptoProvider.
105
+ * This implementation is mobile-safe and works across all platforms (Browser, Node.js, React Native).
106
+ *
107
+ * Output format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
108
+ * where x is any hexadecimal digit and y is one of 8, 9, a, or b.
109
+ *
110
+ * @param provider - Optional custom provider (uses CryptoContext if not provided)
111
+ * @returns UUID v4 string (36 characters including dashes)
112
+ *
113
+ * @example
114
+ * // Default behavior (uses CryptoContext)
115
+ * const uuid = generateUUID();
116
+ * // Example output: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
117
+ *
118
+ * @example
119
+ * // With custom provider
120
+ * const provider = new NobleCryptoProvider();
121
+ * const uuid = generateUUID(provider);
122
+ */
123
+ export function generateUUID(provider?: CryptoProvider): string {
124
+ const bytes = randomBytes(16, provider);
125
+
126
+ // Set version (4) and variant bits according to RFC 4122
127
+ bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
128
+ bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10
129
+
130
+ // Convert to hex string with dashes
131
+ const hex = Array.from(bytes, (byte: number) => byte.toString(16).padStart(2, '0')).join('');
132
+ return [
133
+ hex.slice(0, 8),
134
+ hex.slice(8, 12),
135
+ hex.slice(12, 16),
136
+ hex.slice(16, 20),
137
+ hex.slice(20, 32)
138
+ ].join('-');
139
+ }
140
+
141
+ /**
142
+ * Detect the current platform/runtime environment
143
+ *
144
+ * Provides information about whether code is running in Node.js, browser,
145
+ * React Native, or other JavaScript environments.
146
+ *
147
+ * @returns Platform detection result with environment flags
148
+ *
149
+ * @example
150
+ * const platform = detectPlatform();
151
+ * if (platform.isNode) {
152
+ * console.log('Running in Node.js');
153
+ * } else if (platform.isBrowser) {
154
+ * console.log('Running in browser');
155
+ * }
156
+ */
157
+ export function detectPlatform(): PlatformDetectionResult {
158
+ // Check for Node.js
159
+ const isNode = typeof process !== 'undefined' &&
160
+ process.versions != null &&
161
+ process.versions.node != null;
162
+
163
+ // Check for browser
164
+ const isBrowser = typeof window !== 'undefined' &&
165
+ typeof document !== 'undefined';
166
+
167
+ // Check for React Native
168
+ const isReactNative = typeof navigator !== 'undefined' &&
169
+ navigator.product === 'ReactNative';
170
+
171
+ // Get platform identifier (Node.js only)
172
+ let platform: string | undefined;
173
+ if (isNode && typeof process.platform === 'string') {
174
+ platform = process.platform;
175
+ }
176
+
177
+ // Get user agent (browser/React Native)
178
+ let userAgent: string | undefined;
179
+ if (typeof navigator !== 'undefined' && typeof navigator.userAgent === 'string') {
180
+ userAgent = navigator.userAgent;
181
+ }
182
+
183
+ return {
184
+ isNode,
185
+ isBrowser,
186
+ isReactNative,
187
+ platform,
188
+ userAgent,
189
+ };
190
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * BananaLink Protocol Crypto Export
3
+ *
4
+ * Main export point for cryptographic implementations.
5
+ * Import from '@bananalink-sdk/protocol/crypto' to access these utilities.
6
+ *
7
+ * Tree-Shaking Strategy:
8
+ * - Consumers of @bananalink-sdk/protocol (main export) do NOT bundle crypto
9
+ * - Only consumers of @bananalink-sdk/protocol/crypto include these implementations
10
+ * - Provider implementations are tree-shakeable via factory functions
11
+ *
12
+ * Bundle Size Impact:
13
+ * - WebCrypto provider: ~2KB (uses native APIs)
14
+ * - Noble provider: ~45KB (pure JS fallback)
15
+ * - QuickCrypto provider: ~3KB (React Native bindings)
16
+ * - SessionSecurity: ~5KB
17
+ * - Total (with one provider): ~7-50KB depending on provider
18
+ */
19
+
20
+ // Re-export everything from crypto module
21
+ export * from './crypto';
package/src/index.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * BananaLink Protocol Package
3
+ *
4
+ * Core TypeScript implementation of the BananaLink protocol for dApp-to-wallet communication.
5
+ * Provides types, schemas, constants, and utilities for implementing BananaLink clients and wallets.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ // Core protocol types
11
+ export * from './types/core';
12
+ export * from './types/auth';
13
+ export * from './types/crypto';
14
+ export * from './types/discovery';
15
+ export * from './types/errors';
16
+ export * from './types/providers';
17
+ export * from './types/wallet-messages';
18
+ export * from './types/client-messages';
19
+ export * from './types/relay-messages';
20
+
21
+ // Note: Zod validation schemas have been moved to '@bananalink-sdk/protocol/schemas'
22
+ // For runtime validation, import from '@bananalink-sdk/protocol/schemas'
23
+ // Type-only definitions remain available from this main export
24
+
25
+ // Note: SIWE utilities have been moved to '@bananalink-sdk/protocol/siwe'
26
+ // For ERC-4361 message construction/parsing, import from '@bananalink-sdk/protocol/siwe'
27
+ // Export SIWE types for convenience (type-only, no runtime code)
28
+ export type { SIWEMessageOptions } from './utils/siwe';
29
+
30
+ // Constants and utilities
31
+ export * from './constants';
32
+ export * from './utils';
33
+
34
+ // Package metadata
35
+ // Note: Version is imported from package.json to ensure consistency
36
+ import packageJson from '../package.json';
37
+ export const VERSION = packageJson.version;
38
+ export const PACKAGE_NAME = "@bananalink-sdk/protocol";
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Authentication validation schemas for BananaLink protocol
3
+ */
4
+
5
+ import { z } from 'zod';
6
+
7
+ // Base validation schemas
8
+ const ethereumAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address');
9
+ const iso8601Schema = z.string().datetime({ message: 'Invalid ISO 8601 datetime' });
10
+ const domainSchema = z.string().min(1, 'Domain cannot be empty');
11
+
12
+ // Import schemas from core
13
+ import { dAppMetadataSchema, sessionConfigSchema } from './core';
14
+
15
+ // Session state schema
16
+ export const sessionStateSchema = z.enum([
17
+ 'created',
18
+ 'claimed',
19
+ 'authenticated',
20
+ 'active',
21
+ 'disconnected',
22
+ 'closed',
23
+ 'expired',
24
+ 'rejected',
25
+ ]);
26
+
27
+ // Session info schema
28
+ export const sessionInfoSchema = z.object({
29
+ sessionId: z.string().min(1, 'Session ID cannot be empty'),
30
+ state: sessionStateSchema,
31
+ address: ethereumAddressSchema.optional(),
32
+ dappId: z.string().min(1, 'DApp ID cannot be empty'),
33
+ dappInfo: dAppMetadataSchema.pick({ name: true, url: true }),
34
+ sessionConfig: sessionConfigSchema.optional(),
35
+ createdAt: iso8601Schema,
36
+ lastActivity: iso8601Schema,
37
+ expiresAt: iso8601Schema.optional(),
38
+ });
39
+
40
+ // Origin proof schema
41
+ export const originProofSchema = z.object({
42
+ domain: domainSchema,
43
+ timestamp: z.number().int().positive(),
44
+ signature: z.string().min(1, 'Signature cannot be empty'),
45
+ });
46
+
47
+ // Session ID validation (UUID v4)
48
+ export const sessionIdSchema = z.string().regex(
49
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
50
+ 'Invalid session ID format'
51
+ );
52
+
53
+ // Validation helper functions
54
+ export const validateSessionInfo = (info: unknown) => sessionInfoSchema.parse(info);
55
+
56
+ // Safe parsing functions
57
+ export const safeValidateSessionInfo = (info: unknown) => sessionInfoSchema.safeParse(info);
58
+
59
+ // Note: Types are exported from ./types/auth to avoid conflicts
60
+ // Use z.infer<typeof schemaName> if you need to infer types from schemas
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Validation schemas for client message types
3
+ */
4
+
5
+ import { z } from 'zod';
6
+
7
+ // Client session claim schema
8
+ export const clientSessionClaimSchema = z.object({
9
+ /** Unique nonce for this client session */
10
+ claimNonce: z.string().min(32, 'Client claim nonce must be at least 32 characters'),
11
+ /** Claim generation timestamp */
12
+ timestamp: z.number().int().positive('Timestamp must be a positive integer'),
13
+ });
14
+
15
+ // Client message payload schemas
16
+ export const clientReconnectPayloadSchema = z.object({
17
+ type: z.literal('client_reconnect'),
18
+ });
19
+
20
+ export const closeSessionPayloadSchema = z.object({
21
+ type: z.literal('close_session'),
22
+ /** Optional reason for closing the session */
23
+ reason: z.string().optional(),
24
+ });
25
+
26
+ // Union of all client message payloads
27
+ export const clientMessagePayloadSchema = z.discriminatedUnion('type', [
28
+ clientReconnectPayloadSchema,
29
+ closeSessionPayloadSchema,
30
+ ]);
31
+
32
+ // Client message envelope schema
33
+ export const clientMessageEnvelopeSchema = z.object({
34
+ /** Session identifier */
35
+ sessionId: z.string().uuid('Session ID must be a valid UUID'),
36
+ /** Client session claim */
37
+ clientSessionClaim: clientSessionClaimSchema,
38
+ /** Message payload */
39
+ payload: clientMessagePayloadSchema,
40
+ });
41
+
42
+ // Validation helper functions
43
+ export const validateClientMessage = (message: unknown) => {
44
+ return clientMessageEnvelopeSchema.parse(message);
45
+ };
46
+
47
+ export const safeValidateClientMessage = (message: unknown) => {
48
+ return clientMessageEnvelopeSchema.safeParse(message);
49
+ };
50
+
51
+ export const validateClientSessionClaim = (claim: unknown) => {
52
+ return clientSessionClaimSchema.parse(claim);
53
+ };
54
+
55
+ export const safeValidateClientSessionClaim = (claim: unknown) => {
56
+ return clientSessionClaimSchema.safeParse(claim);
57
+ };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Core validation schemas for BananaLink protocol types
3
+ */
4
+
5
+ import { z } from 'zod';
6
+
7
+ // Base validation schemas
8
+ const ethereumAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address');
9
+ const hexStringSchema = z.string().regex(/^0x[a-fA-F0-9]*$/, 'Invalid hex string');
10
+ const iso8601Schema = z.string().datetime({ message: 'Invalid ISO 8601 datetime' });
11
+ const urlSchema = z.string().url('Invalid URL');
12
+ const domainSchema = z.string().min(1, 'Domain cannot be empty');
13
+ // Protocol v2.0: 32-byte nonces = 64 hex characters
14
+ const nonceSchema = z
15
+ .string()
16
+ .length(64, 'Nonce must be exactly 64 characters (32 bytes hex-encoded)')
17
+ .regex(/^[a-fA-F0-9]{64}$/, 'Nonce must be a valid 64-character hexadecimal string');
18
+
19
+ // SIWE base schema
20
+ export const siweFieldsSchema = z.object({
21
+ scheme: z.string().optional(),
22
+ domain: domainSchema,
23
+ address: ethereumAddressSchema,
24
+ statement: z.string().optional(),
25
+ uri: urlSchema,
26
+ version: z.literal('1'),
27
+ chainId: z.number().int().positive(),
28
+ nonce: nonceSchema,
29
+ issuedAt: iso8601Schema,
30
+ expirationTime: iso8601Schema.optional(),
31
+ notBefore: iso8601Schema.optional(),
32
+ requestId: z.string().optional(),
33
+ resources: z.array(urlSchema).optional(),
34
+ });
35
+
36
+ // Transaction payload schema
37
+ export const transactionPayloadSchema = z.object({
38
+ to: ethereumAddressSchema,
39
+ value: z.string().regex(/^\d+$/, 'Value must be a numeric string').optional(),
40
+ data: hexStringSchema.optional(),
41
+ gas: z.string().regex(/^\d+$/, 'Gas must be a numeric string').optional(),
42
+ gasPrice: z.string().regex(/^\d+$/, 'Gas price must be a numeric string').optional(),
43
+ maxFeePerGas: z.string().regex(/^\d+$/, 'Max fee per gas must be a numeric string').optional(),
44
+ maxPriorityFeePerGas: z.string().regex(/^\d+$/, 'Max priority fee per gas must be a numeric string').optional(),
45
+ });
46
+
47
+ // Sign payload schema
48
+ export const signPayloadSchema = z.object({
49
+ message: z.string().min(1, 'Message cannot be empty'),
50
+ encoding: z.enum(['utf8', 'hex']).default('utf8'),
51
+ });
52
+
53
+ // BananaLink message type schema
54
+ export const messageTypeSchema = z.enum(['auth', 'tx', 'sign']);
55
+
56
+ // Main BananaLink message schema
57
+ export const bananaLinkMessageSchema = siweFieldsSchema.extend({
58
+ type: messageTypeSchema,
59
+ sessionId: z.string().min(1, 'Session ID cannot be empty'),
60
+ payload: z.union([transactionPayloadSchema, signPayloadSchema]).optional(),
61
+ });
62
+
63
+ // DApp metadata schema
64
+ export const dAppMetadataSchema = z.object({
65
+ dappId: z.string().optional(),
66
+ name: z.string().min(1, 'Name cannot be empty'),
67
+ description: z.string().optional(),
68
+ url: urlSchema,
69
+ icons: z.array(urlSchema).optional(),
70
+ });
71
+
72
+ // Session configuration schema
73
+ export const sessionConfigSchema = z.object({
74
+ returnUrl: urlSchema.optional(),
75
+ sessionName: z.string().optional(),
76
+ expiresAt: iso8601Schema.optional(),
77
+ customData: z.record(z.unknown()).optional(),
78
+ });
79
+
80
+ // BananaLink response schema
81
+ export const bananaLinkResponseSchema = z.object({
82
+ success: z.boolean(),
83
+ data: z.unknown().optional(),
84
+ error: z.string().optional(),
85
+ requestId: z.string().optional(),
86
+ });
87
+
88
+ // Security policy schema
89
+ export const securityPolicySchema = z.object({
90
+ sessionTimeout: z.number().int().positive(),
91
+ requestTimeout: z.number().int().positive(),
92
+ maxConcurrentSessions: z.number().int().positive(),
93
+ requireOriginProof: z.boolean(),
94
+ allowedDomains: z.array(domainSchema).optional(),
95
+ blockedDomains: z.array(domainSchema).optional(),
96
+ });
97
+
98
+ // Session options schema
99
+ export const sessionOptionsSchema = z.object({
100
+ config: sessionConfigSchema.optional(),
101
+ securityPolicy: securityPolicySchema.partial().optional(),
102
+ relayUrl: urlSchema.optional(),
103
+ });
104
+
105
+ // Session metadata schema (for session creation)
106
+ export const sessionMetadataSchema = z.object({
107
+ encryptionAlgorithm: z.enum(['AES-GCM', 'plaintext']),
108
+ sessionId: z.string().min(1, 'Session ID cannot be empty'),
109
+ publicKey: z.string().optional(),
110
+ timestamp: iso8601Schema,
111
+ });
112
+
113
+ // Session creation request schema (Client → API Gateway)
114
+ export const createSessionRequestSchema = z.object({
115
+ dappId: z.string().uuid('Invalid DApp ID format'),
116
+ metadata: sessionMetadataSchema,
117
+ sessionConfig: sessionConfigSchema.optional(),
118
+ });
119
+
120
+ // Client session claim schema
121
+ export const clientSessionClaimSchema = z.object({
122
+ /** Unique nonce for client session claim (32 bytes hex-encoded = 64 characters) */
123
+ claimNonce: z
124
+ .string()
125
+ .length(64, 'Claim nonce must be exactly 64 characters (32 bytes hex-encoded)')
126
+ .regex(/^[0-9a-f]{64}$/, 'Claim nonce must be lowercase hexadecimal'),
127
+ timestamp: z.number().int().positive(),
128
+ });
129
+
130
+ // Session creation response schema (API Gateway → Client)
131
+ export const createSessionResponseSchema = z.object({
132
+ sessionId: z.string().uuid('Invalid session ID format'),
133
+ clientSessionClaim: clientSessionClaimSchema,
134
+ ttl: z.number().int().positive(),
135
+ });
136
+
137
+ // Validation helper functions
138
+ export const validateMessage = (message: unknown) => bananaLinkMessageSchema.parse(message);
139
+
140
+ // Safe parsing functions
141
+ export const safeValidateMessage = (message: unknown) => bananaLinkMessageSchema.safeParse(message);
142
+
143
+ // Note: Types are exported from ./types/core to avoid conflicts
144
+ // Use z.infer<typeof schemaName> if you need to infer types from schemas
@@ -0,0 +1,65 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Zod schemas for crypto payload validation
5
+ */
6
+
7
+ export const KeyExchangeSchema = z.object({
8
+ algorithm: z.literal('ECDH'),
9
+ namedCurve: z.literal('P-256'),
10
+ });
11
+
12
+ export const EncryptionSchema = z.object({
13
+ algorithm: z.enum(['AES-GCM', 'plaintext']),
14
+ keyLength: z.literal(256).optional(),
15
+ tagLength: z.literal(128).optional(),
16
+ });
17
+
18
+ export const PublicKeyJWKSchema = z.object({
19
+ kty: z.literal('EC'),
20
+ crv: z.literal('P-256'),
21
+ x: z.string().min(1),
22
+ y: z.string().min(1),
23
+ use: z.literal('enc'),
24
+ key_ops: z.tuple([z.literal('deriveKey')]),
25
+ });
26
+
27
+ export const CryptoParametersSchema = z.object({
28
+ iv: z.string().optional(),
29
+ timestamp: z.string().datetime(),
30
+ sessionId: z.string().uuid(),
31
+ });
32
+
33
+ export const KeyDerivationSchema = z.object({
34
+ algorithm: z.literal('HKDF'),
35
+ hash: z.literal('SHA-256'),
36
+ info: z.literal('message-exchange-v1'),
37
+ salt: z.string().min(1),
38
+ });
39
+
40
+ export const ProductionCryptoPayloadSchema = z.object({
41
+ version: z.literal('1.0'),
42
+ keyExchange: KeyExchangeSchema,
43
+ encryption: EncryptionSchema.extend({
44
+ algorithm: z.literal('AES-GCM'),
45
+ }),
46
+ publicKey: PublicKeyJWKSchema,
47
+ parameters: CryptoParametersSchema,
48
+ derivation: KeyDerivationSchema,
49
+ });
50
+
51
+ export const DevelopmentCryptoPayloadSchema = z.object({
52
+ version: z.literal('1.0'),
53
+ encryption: EncryptionSchema.extend({
54
+ algorithm: z.literal('plaintext'),
55
+ }),
56
+ parameters: CryptoParametersSchema,
57
+ });
58
+
59
+ export const CryptoPayloadSchema = z.union([
60
+ ProductionCryptoPayloadSchema,
61
+ DevelopmentCryptoPayloadSchema,
62
+ ]);
63
+
64
+ // Note: Types are exported from ./types/crypto to avoid conflicts
65
+ // Use z.infer<typeof schemaName> if you need to infer types from schemas
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Discovery validation schemas for BananaLink protocol
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import { FRUITS } from '../constants';
7
+
8
+ // Base validation schemas
9
+ const urlSchema = z.string().url('Invalid URL');
10
+
11
+
12
+ // QR payload schema
13
+ export const qrPayloadSchema = z.object({
14
+ sessionId: z.string().min(1, 'Session ID cannot be empty'),
15
+ publicKey: z.string().min(1, 'Public key cannot be empty'),
16
+ relayUrl: z.string().url().optional(),
17
+ providerId: z.string().optional(),
18
+ });
19
+
20
+ // Encrypted payload schema
21
+ export const encryptedPayloadSchema = z.object({
22
+ iv: z.string().min(1, 'IV cannot be empty'),
23
+ ciphertext: z.string().min(1, 'Ciphertext cannot be empty'),
24
+ mac: z.string().min(1, 'MAC cannot be empty'),
25
+ });
26
+
27
+ // Relay message type schema
28
+ export const relayMessageTypeSchema = z.enum(['pub', 'sub', 'ack', 'error']);
29
+
30
+ // Relay message schema
31
+ export const relayMessageSchema = z.object({
32
+ topic: z.string().min(1, 'Topic cannot be empty'),
33
+ type: relayMessageTypeSchema,
34
+ payload: encryptedPayloadSchema.optional(),
35
+ id: z.string().min(1, 'Message ID cannot be empty'),
36
+ });
37
+
38
+ // Display info schema
39
+ export const displayInfoSchema = z.object({
40
+ qrCode: z.string().min(1, 'QR code cannot be empty'),
41
+ frutiLink: z.string().regex(/^[🍌🍎🍊🍇🍓🍑🍒🍉🥝🍍🥭🥥🫐🍈🍋🥑]{8}$/u, 'Invalid FrutiLink format'),
42
+ deepLink: urlSchema,
43
+ });
44
+
45
+ // FrutiLink validation
46
+ export const frutiLinkSchema = z.string()
47
+ .length(8, 'FrutiLink must be exactly 8 characters')
48
+ .refine(
49
+ (value) => value.split('').every(char => FRUITS.includes(char as typeof FRUITS[number])),
50
+ 'FrutiLink contains invalid fruits'
51
+ );
52
+
53
+ // Public key validation (base64 encoded)
54
+ export const publicKeySchema = z.string()
55
+ .min(1, 'Public key cannot be empty')
56
+ .refine(
57
+ (key) => {
58
+ try {
59
+ atob(key);
60
+ return true;
61
+ } catch {
62
+ return false;
63
+ }
64
+ },
65
+ 'Public key must be valid Base64'
66
+ );
67
+
68
+ // Validation helper functions
69
+ export const validateQRPayload = (payload: unknown) => qrPayloadSchema.parse(payload);
70
+ export const validateRelayMessage = (message: unknown) => relayMessageSchema.parse(message);
71
+ export const validateFrutiLink = (frutiLink: unknown) => frutiLinkSchema.parse(frutiLink);
72
+
73
+ // Safe parsing functions
74
+ export const safeValidateQRPayload = (payload: unknown) => qrPayloadSchema.safeParse(payload);
75
+ export const safeValidateRelayMessage = (message: unknown) => relayMessageSchema.safeParse(message);
76
+ export const safeValidateFrutiLink = (frutiLink: unknown) => frutiLinkSchema.safeParse(frutiLink);
77
+
78
+ // Note: Types are exported from ./types/discovery to avoid conflicts
79
+ // Use z.infer<typeof schemaName> if you need to infer types from schemas