@ruvector/edge-net 0.4.5 → 0.5.0
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/credits.js +631 -0
- package/package.json +21 -2
- package/plugins/cli.js +395 -0
- package/plugins/implementations/compression.js +132 -0
- package/plugins/implementations/e2e-encryption.js +160 -0
- package/plugins/implementations/federated-learning.js +249 -0
- package/plugins/implementations/reputation-staking.js +243 -0
- package/plugins/implementations/swarm-intelligence.js +386 -0
- package/plugins/index.js +90 -0
- package/plugins/plugin-loader.js +440 -0
- package/plugins/plugin-manifest.js +702 -0
- package/plugins/plugin-sdk.js +496 -0
- package/task-execution-handler.js +868 -0
- package/tests/plugin-system-test.js +382 -0
- package/tests/webrtc-datachannel-e2e-test.js +1081 -0
package/plugins/cli.js
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Edge-Net Plugin CLI
|
|
4
|
+
*
|
|
5
|
+
* Manage plugins from the command line.
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* edge-net plugins list List available plugins
|
|
9
|
+
* edge-net plugins info <id> Show plugin details
|
|
10
|
+
* edge-net plugins enable <id> Enable a plugin
|
|
11
|
+
* edge-net plugins disable <id> Disable a plugin
|
|
12
|
+
* edge-net plugins bundles List plugin bundles
|
|
13
|
+
* edge-net plugins create <name> Create new plugin from template
|
|
14
|
+
* edge-net plugins validate <path> Validate a custom plugin
|
|
15
|
+
*
|
|
16
|
+
* @module @ruvector/edge-net/plugins/cli
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
20
|
+
import { join, dirname } from 'path';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
import {
|
|
23
|
+
PLUGIN_CATALOG,
|
|
24
|
+
PLUGIN_BUNDLES,
|
|
25
|
+
PluginCategory,
|
|
26
|
+
PluginTier,
|
|
27
|
+
} from './plugin-manifest.js';
|
|
28
|
+
import { PluginManager } from './plugin-loader.js';
|
|
29
|
+
import { generatePluginTemplate, validateManifest, getRegistry } from './plugin-sdk.js';
|
|
30
|
+
|
|
31
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
|
|
33
|
+
// ============================================
|
|
34
|
+
// CLI COMMANDS
|
|
35
|
+
// ============================================
|
|
36
|
+
|
|
37
|
+
const commands = {
|
|
38
|
+
/**
|
|
39
|
+
* List all available plugins
|
|
40
|
+
*/
|
|
41
|
+
list: async (args) => {
|
|
42
|
+
const filter = args[0]; // Optional: category filter
|
|
43
|
+
|
|
44
|
+
console.log('\n╔════════════════════════════════════════════════════════════════╗');
|
|
45
|
+
console.log('║ EDGE-NET PLUGIN CATALOG ║');
|
|
46
|
+
console.log('╚════════════════════════════════════════════════════════════════╝\n');
|
|
47
|
+
|
|
48
|
+
const plugins = Object.values(PLUGIN_CATALOG);
|
|
49
|
+
const grouped = {};
|
|
50
|
+
|
|
51
|
+
for (const plugin of plugins) {
|
|
52
|
+
if (filter && plugin.category !== filter && plugin.tier !== filter) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (!grouped[plugin.category]) {
|
|
56
|
+
grouped[plugin.category] = [];
|
|
57
|
+
}
|
|
58
|
+
grouped[plugin.category].push(plugin);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const [category, categoryPlugins] of Object.entries(grouped)) {
|
|
62
|
+
console.log(`\n┌─ ${category.toUpperCase()} ─────────────────────────────────────────┐`);
|
|
63
|
+
|
|
64
|
+
for (const plugin of categoryPlugins) {
|
|
65
|
+
const tier = getTierBadge(plugin.tier);
|
|
66
|
+
const status = getStatusIcon(plugin);
|
|
67
|
+
console.log(`│ ${status} ${plugin.id.padEnd(30)} ${tier}`);
|
|
68
|
+
console.log(`│ ${plugin.description.slice(0, 55)}...`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log('└────────────────────────────────────────────────────────┘');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(`\n📦 Total: ${plugins.length} plugins in ${Object.keys(grouped).length} categories\n`);
|
|
75
|
+
console.log('Use: edge-net plugins info <id> for details');
|
|
76
|
+
console.log('Use: edge-net plugins enable <id> to enable a plugin\n');
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Show plugin details
|
|
81
|
+
*/
|
|
82
|
+
info: async (args) => {
|
|
83
|
+
const pluginId = args[0];
|
|
84
|
+
if (!pluginId) {
|
|
85
|
+
console.error('Usage: edge-net plugins info <plugin-id>');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const plugin = PLUGIN_CATALOG[pluginId];
|
|
90
|
+
if (!plugin) {
|
|
91
|
+
console.error(`Plugin not found: ${pluginId}`);
|
|
92
|
+
console.log('\nAvailable plugins:');
|
|
93
|
+
Object.keys(PLUGIN_CATALOG).forEach(id => console.log(` - ${id}`));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log('\n╔════════════════════════════════════════════════════════════════╗');
|
|
98
|
+
console.log(`║ ${plugin.name.padEnd(60)}║`);
|
|
99
|
+
console.log('╚════════════════════════════════════════════════════════════════╝\n');
|
|
100
|
+
|
|
101
|
+
console.log(` ID: ${plugin.id}`);
|
|
102
|
+
console.log(` Version: ${plugin.version}`);
|
|
103
|
+
console.log(` Category: ${plugin.category}`);
|
|
104
|
+
console.log(` Tier: ${getTierBadge(plugin.tier)}`);
|
|
105
|
+
console.log(` Description: ${plugin.description}`);
|
|
106
|
+
|
|
107
|
+
if (plugin.capabilities?.length > 0) {
|
|
108
|
+
console.log(`\n Capabilities Required:`);
|
|
109
|
+
plugin.capabilities.forEach(cap => console.log(` • ${cap}`));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (plugin.dependencies?.length > 0) {
|
|
113
|
+
console.log(`\n Dependencies:`);
|
|
114
|
+
plugin.dependencies.forEach(dep => console.log(` → ${dep}`));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (plugin.tags?.length > 0) {
|
|
118
|
+
console.log(`\n Tags: ${plugin.tags.join(', ')}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (plugin.configSchema?.properties) {
|
|
122
|
+
console.log('\n Configuration Options:');
|
|
123
|
+
for (const [key, prop] of Object.entries(plugin.configSchema.properties)) {
|
|
124
|
+
const def = prop.default !== undefined ? ` (default: ${JSON.stringify(prop.default)})` : '';
|
|
125
|
+
console.log(` ${key}: ${prop.type}${def}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log('\n Usage:');
|
|
130
|
+
console.log(` const plugin = await plugins.load('${pluginId}');`);
|
|
131
|
+
console.log('');
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* List plugin bundles
|
|
136
|
+
*/
|
|
137
|
+
bundles: async () => {
|
|
138
|
+
console.log('\n╔════════════════════════════════════════════════════════════════╗');
|
|
139
|
+
console.log('║ PLUGIN BUNDLES ║');
|
|
140
|
+
console.log('╚════════════════════════════════════════════════════════════════╝\n');
|
|
141
|
+
|
|
142
|
+
for (const [id, bundle] of Object.entries(PLUGIN_BUNDLES)) {
|
|
143
|
+
console.log(`┌─ ${bundle.name.toUpperCase()} ─────────────────────────────────────────┐`);
|
|
144
|
+
console.log(`│ ID: ${id}`);
|
|
145
|
+
console.log(`│ ${bundle.description}`);
|
|
146
|
+
console.log('│');
|
|
147
|
+
console.log('│ Plugins:');
|
|
148
|
+
if (bundle.plugins.length === 0) {
|
|
149
|
+
console.log('│ (none - minimal bundle)');
|
|
150
|
+
} else {
|
|
151
|
+
bundle.plugins.forEach(p => console.log(`│ • ${p}`));
|
|
152
|
+
}
|
|
153
|
+
console.log('└────────────────────────────────────────────────────────────┘\n');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log('Usage: await plugins.loadBundle("bundle-name")\n');
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Create new plugin from template
|
|
161
|
+
*/
|
|
162
|
+
create: async (args) => {
|
|
163
|
+
const name = args[0];
|
|
164
|
+
if (!name) {
|
|
165
|
+
console.error('Usage: edge-net plugins create <plugin-name>');
|
|
166
|
+
console.log('\nOptions:');
|
|
167
|
+
console.log(' --category <category> Plugin category (default: core)');
|
|
168
|
+
console.log(' --tier <tier> Plugin tier (default: experimental)');
|
|
169
|
+
console.log(' --output <dir> Output directory (default: ./plugins)');
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Parse options
|
|
174
|
+
const category = getArg(args, '--category') || 'core';
|
|
175
|
+
const tier = getArg(args, '--tier') || 'experimental';
|
|
176
|
+
const outputDir = getArg(args, '--output') || './plugins';
|
|
177
|
+
|
|
178
|
+
const id = `custom.${name.toLowerCase().replace(/\s+/g, '-')}`;
|
|
179
|
+
const pascalName = name.split(/[-\s]+/).map(s =>
|
|
180
|
+
s.charAt(0).toUpperCase() + s.slice(1)
|
|
181
|
+
).join('');
|
|
182
|
+
|
|
183
|
+
console.log(`\n🔧 Creating plugin: ${name}\n`);
|
|
184
|
+
|
|
185
|
+
const template = generatePluginTemplate({
|
|
186
|
+
id,
|
|
187
|
+
name,
|
|
188
|
+
description: `Custom edge-net plugin: ${name}`,
|
|
189
|
+
category: PluginCategory[category.toUpperCase()] || PluginCategory.CORE,
|
|
190
|
+
tier: PluginTier[tier.toUpperCase()] || PluginTier.EXPERIMENTAL,
|
|
191
|
+
capabilities: [],
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Create output directory
|
|
195
|
+
const pluginDir = join(outputDir, name.toLowerCase().replace(/\s+/g, '-'));
|
|
196
|
+
if (!existsSync(pluginDir)) {
|
|
197
|
+
mkdirSync(pluginDir, { recursive: true });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Write plugin file
|
|
201
|
+
const pluginFile = join(pluginDir, 'index.js');
|
|
202
|
+
writeFileSync(pluginFile, template);
|
|
203
|
+
|
|
204
|
+
// Write package.json
|
|
205
|
+
const packageJson = {
|
|
206
|
+
name: `@edge-net-plugin/${name.toLowerCase().replace(/\s+/g, '-')}`,
|
|
207
|
+
version: '1.0.0',
|
|
208
|
+
type: 'module',
|
|
209
|
+
main: 'index.js',
|
|
210
|
+
description: `Custom edge-net plugin: ${name}`,
|
|
211
|
+
keywords: ['edge-net', 'plugin', category],
|
|
212
|
+
peerDependencies: {
|
|
213
|
+
'@ruvector/edge-net': '^0.4.0',
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
writeFileSync(join(pluginDir, 'package.json'), JSON.stringify(packageJson, null, 2));
|
|
217
|
+
|
|
218
|
+
// Write README
|
|
219
|
+
const readme = `# ${name}
|
|
220
|
+
|
|
221
|
+
Custom edge-net plugin.
|
|
222
|
+
|
|
223
|
+
## Installation
|
|
224
|
+
|
|
225
|
+
\`\`\`bash
|
|
226
|
+
npm install @edge-net-plugin/${name.toLowerCase().replace(/\s+/g, '-')}
|
|
227
|
+
\`\`\`
|
|
228
|
+
|
|
229
|
+
## Usage
|
|
230
|
+
|
|
231
|
+
\`\`\`javascript
|
|
232
|
+
import { getRegistry } from '@ruvector/edge-net/plugins/sdk';
|
|
233
|
+
import { ${pascalName}Plugin } from './${name.toLowerCase().replace(/\s+/g, '-')}';
|
|
234
|
+
|
|
235
|
+
// Register plugin
|
|
236
|
+
getRegistry().register(${pascalName}Plugin);
|
|
237
|
+
|
|
238
|
+
// Use with plugin manager
|
|
239
|
+
const plugins = PluginManager.getInstance();
|
|
240
|
+
const myPlugin = await plugins.load('${id}');
|
|
241
|
+
\`\`\`
|
|
242
|
+
|
|
243
|
+
## Configuration
|
|
244
|
+
|
|
245
|
+
See \`configSchema\` in the plugin manifest for available options.
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
MIT
|
|
250
|
+
`;
|
|
251
|
+
writeFileSync(join(pluginDir, 'README.md'), readme);
|
|
252
|
+
|
|
253
|
+
console.log(`✅ Created plugin at: ${pluginDir}`);
|
|
254
|
+
console.log('');
|
|
255
|
+
console.log('Files created:');
|
|
256
|
+
console.log(` 📄 ${pluginFile}`);
|
|
257
|
+
console.log(` 📄 ${join(pluginDir, 'package.json')}`);
|
|
258
|
+
console.log(` 📄 ${join(pluginDir, 'README.md')}`);
|
|
259
|
+
console.log('');
|
|
260
|
+
console.log('Next steps:');
|
|
261
|
+
console.log(` 1. Edit ${pluginFile} to add your logic`);
|
|
262
|
+
console.log(' 2. Register with: getRegistry().register(YourPlugin)');
|
|
263
|
+
console.log(' 3. Load with: plugins.load("' + id + '")');
|
|
264
|
+
console.log('');
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Validate a plugin
|
|
269
|
+
*/
|
|
270
|
+
validate: async (args) => {
|
|
271
|
+
const pluginPath = args[0];
|
|
272
|
+
if (!pluginPath) {
|
|
273
|
+
console.error('Usage: edge-net plugins validate <plugin-path>');
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
console.log(`\n🔍 Validating plugin: ${pluginPath}\n`);
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const plugin = await import(pluginPath);
|
|
281
|
+
const PluginClass = plugin.default || Object.values(plugin)[0];
|
|
282
|
+
|
|
283
|
+
if (!PluginClass) {
|
|
284
|
+
console.error('❌ No plugin class found in module');
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Validate manifest
|
|
289
|
+
if (!PluginClass.manifest) {
|
|
290
|
+
console.error('❌ Plugin missing static manifest property');
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const validation = validateManifest(PluginClass.manifest);
|
|
295
|
+
|
|
296
|
+
if (validation.valid) {
|
|
297
|
+
console.log('✅ Plugin is valid!\n');
|
|
298
|
+
console.log('Manifest:');
|
|
299
|
+
console.log(JSON.stringify(PluginClass.manifest, null, 2));
|
|
300
|
+
} else {
|
|
301
|
+
console.log('❌ Plugin validation failed:\n');
|
|
302
|
+
validation.errors.forEach(err => console.log(` • ${err}`));
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.error(`❌ Error loading plugin: ${error.message}`);
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Show help
|
|
313
|
+
*/
|
|
314
|
+
help: async () => {
|
|
315
|
+
console.log(`
|
|
316
|
+
Edge-Net Plugin Management
|
|
317
|
+
|
|
318
|
+
Usage: edge-net plugins <command> [options]
|
|
319
|
+
|
|
320
|
+
Commands:
|
|
321
|
+
list [category] List available plugins (filter by category/tier)
|
|
322
|
+
info <id> Show detailed plugin information
|
|
323
|
+
bundles List plugin bundles
|
|
324
|
+
create <name> Create new plugin from template
|
|
325
|
+
validate <path> Validate a custom plugin file
|
|
326
|
+
|
|
327
|
+
Categories:
|
|
328
|
+
core, network, crypto, privacy, ai, economic, storage, exotic
|
|
329
|
+
|
|
330
|
+
Tiers:
|
|
331
|
+
stable, beta, experimental, research
|
|
332
|
+
|
|
333
|
+
Examples:
|
|
334
|
+
edge-net plugins list
|
|
335
|
+
edge-net plugins list privacy
|
|
336
|
+
edge-net plugins info crypto.zk-proofs
|
|
337
|
+
edge-net plugins bundles
|
|
338
|
+
edge-net plugins create my-plugin --category ai
|
|
339
|
+
edge-net plugins validate ./my-plugin/index.js
|
|
340
|
+
`);
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// ============================================
|
|
345
|
+
// HELPERS
|
|
346
|
+
// ============================================
|
|
347
|
+
|
|
348
|
+
function getTierBadge(tier) {
|
|
349
|
+
const badges = {
|
|
350
|
+
stable: '🟢 STABLE',
|
|
351
|
+
beta: '🟡 BETA',
|
|
352
|
+
experimental: '🟠 EXPERIMENTAL',
|
|
353
|
+
research: '🔴 RESEARCH',
|
|
354
|
+
};
|
|
355
|
+
return badges[tier] || tier;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function getStatusIcon(plugin) {
|
|
359
|
+
// In real implementation, check if enabled
|
|
360
|
+
return '○';
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function getArg(args, flag) {
|
|
364
|
+
const idx = args.indexOf(flag);
|
|
365
|
+
if (idx >= 0 && args[idx + 1]) {
|
|
366
|
+
return args[idx + 1];
|
|
367
|
+
}
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ============================================
|
|
372
|
+
// MAIN
|
|
373
|
+
// ============================================
|
|
374
|
+
|
|
375
|
+
async function main() {
|
|
376
|
+
const args = process.argv.slice(2);
|
|
377
|
+
const command = args[0] || 'help';
|
|
378
|
+
const commandArgs = args.slice(1);
|
|
379
|
+
|
|
380
|
+
if (commands[command]) {
|
|
381
|
+
await commands[command](commandArgs);
|
|
382
|
+
} else {
|
|
383
|
+
console.error(`Unknown command: ${command}`);
|
|
384
|
+
await commands.help();
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Run if executed directly
|
|
390
|
+
if (process.argv[1]?.includes('cli.js') || process.argv[1]?.includes('plugins')) {
|
|
391
|
+
main().catch(console.error);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export { commands };
|
|
395
|
+
export default main;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Compression Plugin
|
|
3
|
+
*
|
|
4
|
+
* LZ4/Zstd compression for network payloads and storage.
|
|
5
|
+
* Uses WASM for high-performance compression.
|
|
6
|
+
*
|
|
7
|
+
* @module @ruvector/edge-net/plugins/compression
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Simple LZ4-like compression (for demo - use actual WASM in production)
|
|
11
|
+
export class CompressionPlugin {
|
|
12
|
+
constructor(config = {}) {
|
|
13
|
+
this.config = {
|
|
14
|
+
algorithm: config.algorithm || 'lz4',
|
|
15
|
+
level: config.level || 3,
|
|
16
|
+
threshold: config.threshold || 1024,
|
|
17
|
+
};
|
|
18
|
+
this.stats = {
|
|
19
|
+
compressed: 0,
|
|
20
|
+
decompressed: 0,
|
|
21
|
+
bytesIn: 0,
|
|
22
|
+
bytesOut: 0,
|
|
23
|
+
ratio: 0,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Compress data if above threshold
|
|
29
|
+
*/
|
|
30
|
+
compress(data) {
|
|
31
|
+
const input = typeof data === 'string' ? Buffer.from(data) : data;
|
|
32
|
+
|
|
33
|
+
if (input.length < this.config.threshold) {
|
|
34
|
+
return { compressed: false, data: input };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Simple RLE-based compression (demo)
|
|
38
|
+
const compressed = this._rleCompress(input);
|
|
39
|
+
|
|
40
|
+
this.stats.compressed++;
|
|
41
|
+
this.stats.bytesIn += input.length;
|
|
42
|
+
this.stats.bytesOut += compressed.length;
|
|
43
|
+
this.stats.ratio = this.stats.bytesOut / this.stats.bytesIn;
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
compressed: true,
|
|
47
|
+
data: compressed,
|
|
48
|
+
originalSize: input.length,
|
|
49
|
+
compressedSize: compressed.length,
|
|
50
|
+
ratio: compressed.length / input.length,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Decompress data
|
|
56
|
+
*/
|
|
57
|
+
decompress(data, wasCompressed = true) {
|
|
58
|
+
if (!wasCompressed) {
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const decompressed = this._rleDecompress(data);
|
|
63
|
+
this.stats.decompressed++;
|
|
64
|
+
|
|
65
|
+
return decompressed;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Simple RLE compression (for demo)
|
|
70
|
+
*/
|
|
71
|
+
_rleCompress(input) {
|
|
72
|
+
const output = [];
|
|
73
|
+
let i = 0;
|
|
74
|
+
|
|
75
|
+
while (i < input.length) {
|
|
76
|
+
const byte = input[i];
|
|
77
|
+
let count = 1;
|
|
78
|
+
|
|
79
|
+
while (i + count < input.length &&
|
|
80
|
+
input[i + count] === byte &&
|
|
81
|
+
count < 255) {
|
|
82
|
+
count++;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (count >= 4) {
|
|
86
|
+
output.push(0xFF, count, byte);
|
|
87
|
+
} else {
|
|
88
|
+
for (let j = 0; j < count; j++) {
|
|
89
|
+
if (byte === 0xFF) {
|
|
90
|
+
output.push(0xFF, 1, byte);
|
|
91
|
+
} else {
|
|
92
|
+
output.push(byte);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
i += count;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return Buffer.from(output);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Simple RLE decompression
|
|
105
|
+
*/
|
|
106
|
+
_rleDecompress(input) {
|
|
107
|
+
const output = [];
|
|
108
|
+
let i = 0;
|
|
109
|
+
|
|
110
|
+
while (i < input.length) {
|
|
111
|
+
if (input[i] === 0xFF && i + 2 < input.length) {
|
|
112
|
+
const count = input[i + 1];
|
|
113
|
+
const byte = input[i + 2];
|
|
114
|
+
for (let j = 0; j < count; j++) {
|
|
115
|
+
output.push(byte);
|
|
116
|
+
}
|
|
117
|
+
i += 3;
|
|
118
|
+
} else {
|
|
119
|
+
output.push(input[i]);
|
|
120
|
+
i++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return Buffer.from(output);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getStats() {
|
|
128
|
+
return this.stats;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export default CompressionPlugin;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-End Encryption Plugin
|
|
3
|
+
*
|
|
4
|
+
* X25519 key exchange + ChaCha20-Poly1305 encryption.
|
|
5
|
+
* Provides forward secrecy with automatic key rotation.
|
|
6
|
+
*
|
|
7
|
+
* @module @ruvector/edge-net/plugins/e2e-encryption
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { randomBytes, createCipheriv, createDecipheriv, createHash } from 'crypto';
|
|
11
|
+
|
|
12
|
+
export class E2EEncryptionPlugin {
|
|
13
|
+
constructor(config = {}) {
|
|
14
|
+
this.config = {
|
|
15
|
+
keyRotationInterval: config.keyRotationInterval || 3600000, // 1 hour
|
|
16
|
+
forwardSecrecy: config.forwardSecrecy ?? true,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Session keys (in production, use proper X25519)
|
|
20
|
+
this.sessionKeys = new Map(); // peerId -> { key, iv, createdAt }
|
|
21
|
+
this.rotationTimer = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async init() {
|
|
25
|
+
if (this.config.forwardSecrecy) {
|
|
26
|
+
this.rotationTimer = setInterval(
|
|
27
|
+
() => this._rotateKeys(),
|
|
28
|
+
this.config.keyRotationInterval
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async destroy() {
|
|
34
|
+
if (this.rotationTimer) {
|
|
35
|
+
clearInterval(this.rotationTimer);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Establish encrypted session with peer
|
|
41
|
+
*/
|
|
42
|
+
async establishSession(peerId, peerPublicKey) {
|
|
43
|
+
// In production: X25519 key exchange
|
|
44
|
+
// For demo: Generate shared secret from peer ID
|
|
45
|
+
const sharedSecret = createHash('sha256')
|
|
46
|
+
.update(peerId + '-' + Date.now())
|
|
47
|
+
.digest();
|
|
48
|
+
|
|
49
|
+
const sessionKey = {
|
|
50
|
+
key: sharedSecret,
|
|
51
|
+
iv: randomBytes(16),
|
|
52
|
+
createdAt: Date.now(),
|
|
53
|
+
messageCount: 0,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
this.sessionKeys.set(peerId, sessionKey);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
sessionId: createHash('sha256').update(sharedSecret).digest('hex').slice(0, 16),
|
|
60
|
+
publicKey: randomBytes(32).toString('hex'), // Our ephemeral public key
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Encrypt message for peer
|
|
66
|
+
*/
|
|
67
|
+
encrypt(peerId, plaintext) {
|
|
68
|
+
const session = this.sessionKeys.get(peerId);
|
|
69
|
+
if (!session) {
|
|
70
|
+
throw new Error(`No session with peer: ${peerId}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Use AES-256-GCM (ChaCha20-Poly1305 in production)
|
|
74
|
+
const iv = randomBytes(12);
|
|
75
|
+
const cipher = createCipheriv('aes-256-gcm', session.key, iv);
|
|
76
|
+
|
|
77
|
+
const data = typeof plaintext === 'string' ? plaintext : JSON.stringify(plaintext);
|
|
78
|
+
const encrypted = Buffer.concat([
|
|
79
|
+
cipher.update(data, 'utf8'),
|
|
80
|
+
cipher.final(),
|
|
81
|
+
]);
|
|
82
|
+
const authTag = cipher.getAuthTag();
|
|
83
|
+
|
|
84
|
+
session.messageCount++;
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
iv: iv.toString('base64'),
|
|
88
|
+
ciphertext: encrypted.toString('base64'),
|
|
89
|
+
authTag: authTag.toString('base64'),
|
|
90
|
+
messageNum: session.messageCount,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Decrypt message from peer
|
|
96
|
+
*/
|
|
97
|
+
decrypt(peerId, encryptedMessage) {
|
|
98
|
+
const session = this.sessionKeys.get(peerId);
|
|
99
|
+
if (!session) {
|
|
100
|
+
throw new Error(`No session with peer: ${peerId}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const iv = Buffer.from(encryptedMessage.iv, 'base64');
|
|
104
|
+
const ciphertext = Buffer.from(encryptedMessage.ciphertext, 'base64');
|
|
105
|
+
const authTag = Buffer.from(encryptedMessage.authTag, 'base64');
|
|
106
|
+
|
|
107
|
+
const decipher = createDecipheriv('aes-256-gcm', session.key, iv);
|
|
108
|
+
decipher.setAuthTag(authTag);
|
|
109
|
+
|
|
110
|
+
const decrypted = Buffer.concat([
|
|
111
|
+
decipher.update(ciphertext),
|
|
112
|
+
decipher.final(),
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
return decrypted.toString('utf8');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Rotate session keys for forward secrecy
|
|
120
|
+
*/
|
|
121
|
+
_rotateKeys() {
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
for (const [peerId, session] of this.sessionKeys) {
|
|
124
|
+
if (now - session.createdAt > this.config.keyRotationInterval) {
|
|
125
|
+
// Generate new session key
|
|
126
|
+
const newKey = createHash('sha256')
|
|
127
|
+
.update(session.key)
|
|
128
|
+
.update(randomBytes(32))
|
|
129
|
+
.digest();
|
|
130
|
+
|
|
131
|
+
session.key = newKey;
|
|
132
|
+
session.createdAt = now;
|
|
133
|
+
session.messageCount = 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if session exists
|
|
140
|
+
*/
|
|
141
|
+
hasSession(peerId) {
|
|
142
|
+
return this.sessionKeys.has(peerId);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* End session with peer
|
|
147
|
+
*/
|
|
148
|
+
endSession(peerId) {
|
|
149
|
+
return this.sessionKeys.delete(peerId);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getStats() {
|
|
153
|
+
return {
|
|
154
|
+
activeSessions: this.sessionKeys.size,
|
|
155
|
+
rotationInterval: this.config.keyRotationInterval,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export default E2EEncryptionPlugin;
|