@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,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
|