From 8cb5df350f1c9a267ff3788d937d47d397839e0b Mon Sep 17 00:00:00 2001 From: Ayaanshaikh12243 Date: Thu, 5 Mar 2026 23:01:11 +0530 Subject: [PATCH] ISSUE-994 --- federated/client.js | 84 ++++++++++++++++++++++++++++++++ federated/compliance.js | 31 ++++++++++++ federated/config.js | 11 +++++ federated/crypto.js | 71 +++++++++++++++++++++++++++ federated/model.js | 45 +++++++++++++++++ federated/privacy.js | 40 ++++++++++++++++ federated/server.js | 104 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 386 insertions(+) create mode 100644 federated/client.js create mode 100644 federated/compliance.js create mode 100644 federated/config.js create mode 100644 federated/crypto.js create mode 100644 federated/model.js create mode 100644 federated/privacy.js create mode 100644 federated/server.js diff --git a/federated/client.js b/federated/client.js new file mode 100644 index 00000000..990cba8b --- /dev/null +++ b/federated/client.js @@ -0,0 +1,84 @@ +// Federated Learning Client +// Handles local training, secure communication with server + +const net = require('net'); +const crypto = require('./crypto'); +const privacy = require('./privacy'); +const model = require('./model'); +const compliance = require('./compliance'); +const config = require('./config'); + +class FederatedClient { + constructor(options) { + this.serverHost = options.serverHost || 'localhost'; + this.serverPort = options.serverPort || 9000; + this.clientId = options.clientId || crypto.generateClientId(); + this.localModel = model.createModel(); + this.socket = null; + this.auditLog = []; + } + + connect() { + this.socket = net.createConnection({ host: this.serverHost, port: this.serverPort }, () => { + compliance.log('Connected to server', { serverHost: this.serverHost, serverPort: this.serverPort }); + crypto.authenticateServer(this.socket, this.clientId); + }); + this.socket.on('data', (data) => this.handleServerMessage(data)); + this.socket.on('end', () => compliance.log('Disconnected from server', {})); + } + + handleServerMessage(data) { + let message; + try { + message = crypto.decryptMessage(data); + } catch (e) { + compliance.log('Decryption failed', { error: e.message }); + return; + } + switch (message.type) { + case 'globalModel': + this.updateLocalModel(message.model); + break; + default: + compliance.log('Unknown message type', { type: message.type }); + } + } + + updateLocalModel(globalModel) { + this.localModel = model.update(this.localModel, globalModel); + compliance.log('Local model updated', {}); + } + + trainLocalModel(data) { + // Train model on local data + this.localModel = model.train(this.localModel, data); + compliance.log('Local model trained', {}); + } + + sendModelUpdate() { + // Apply privacy mechanisms + const privateModel = privacy.applyDifferentialPrivacy(this.localModel); + const encrypted = crypto.encryptMessage({ + type: 'modelUpdate', + clientId: this.clientId, + model: privateModel + }); + this.socket.write(encrypted); + compliance.log('Model update sent', {}); + } +} + +module.exports = FederatedClient; + +// If run directly, start the client +if (require.main === module) { + const client = new FederatedClient({ + serverHost: config.serverHost, + serverPort: config.serverPort, + clientId: config.clientId + }); + client.connect(); + // Example: train and send update + // client.trainLocalModel(localData); + // client.sendModelUpdate(); +} diff --git a/federated/compliance.js b/federated/compliance.js new file mode 100644 index 00000000..b0c16071 --- /dev/null +++ b/federated/compliance.js @@ -0,0 +1,31 @@ +// Compliance Logging and Audit Trail +// Tracks data access, model updates, user actions + +const fs = require('fs'); +const path = require('path'); +const LOG_FILE = path.join(__dirname, 'audit.log'); + +function log(action, details) { + const entry = { + timestamp: new Date().toISOString(), + action, + details + }; + fs.appendFileSync(LOG_FILE, JSON.stringify(entry) + '\n'); +} + +function getLogs() { + if (!fs.existsSync(LOG_FILE)) return []; + const lines = fs.readFileSync(LOG_FILE, 'utf8').split('\n').filter(Boolean); + return lines.map(line => JSON.parse(line)); +} + +function queryLogs(filterFn) { + return getLogs().filter(filterFn); +} + +module.exports = { + log, + getLogs, + queryLogs +}; diff --git a/federated/config.js b/federated/config.js new file mode 100644 index 00000000..2d604f81 --- /dev/null +++ b/federated/config.js @@ -0,0 +1,11 @@ +// Configuration and Setup Scripts +// Server/client config, environment variables + +const dotenv = require('dotenv'); +dotenv.config(); + +module.exports = { + serverPort: process.env.SERVER_PORT || 9000, + serverHost: process.env.SERVER_HOST || 'localhost', + clientId: process.env.CLIENT_ID || null +}; diff --git a/federated/crypto.js b/federated/crypto.js new file mode 100644 index 00000000..2690f788 --- /dev/null +++ b/federated/crypto.js @@ -0,0 +1,71 @@ +// Secure Communication Utilities +// Encryption, authentication, key management + +const crypto = require('crypto'); + +const ALGORITHM = 'aes-256-gcm'; +const KEY_LENGTH = 32; +const IV_LENGTH = 12; +const TAG_LENGTH = 16; + +let serverKey = crypto.randomBytes(KEY_LENGTH); +let clientKeys = new Map(); // clientId -> key + +function generateClientId() { + return crypto.randomBytes(8).toString('hex'); +} + +function generateKey() { + return crypto.randomBytes(KEY_LENGTH); +} + +function encryptMessage(message, key = serverKey) { + const iv = crypto.randomBytes(IV_LENGTH); + const cipher = crypto.createCipheriv(ALGORITHM, key, iv); + let encrypted = cipher.update(JSON.stringify(message), 'utf8'); + encrypted = Buffer.concat([encrypted, cipher.final()]); + const tag = cipher.getAuthTag(); + return Buffer.concat([iv, tag, encrypted]); +} + +function decryptMessage(buffer, key = serverKey) { + const iv = buffer.slice(0, IV_LENGTH); + const tag = buffer.slice(IV_LENGTH, IV_LENGTH + TAG_LENGTH); + const encrypted = buffer.slice(IV_LENGTH + TAG_LENGTH); + const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); + decipher.setAuthTag(tag); + let decrypted = decipher.update(encrypted); + decrypted = Buffer.concat([decrypted, decipher.final()]); + return JSON.parse(decrypted.toString('utf8')); +} + +function authenticateClient(socket, callback) { + // Simple handshake: client sends ID, server stores key + socket.once('data', (data) => { + const clientId = data.toString('utf8'); + const key = generateKey(); + clientKeys.set(clientId, key); + socket.write(key); + callback(clientId); + }); +} + +function authenticateServer(socket, clientId) { + // Client sends ID, receives key + socket.write(clientId); + socket.once('data', (data) => { + // Store received key for future communication + clientKeys.set(clientId, data); + }); +} + +module.exports = { + generateClientId, + generateKey, + encryptMessage, + decryptMessage, + authenticateClient, + authenticateServer, + clientKeys, + serverKey +}; diff --git a/federated/model.js b/federated/model.js new file mode 100644 index 00000000..e76c123a --- /dev/null +++ b/federated/model.js @@ -0,0 +1,45 @@ +// Model Definition and Training Logic +// Financial analytics model, local training, serialization + +function createModel() { + // Simple linear regression model: weights and bias + return { + weight: Math.random(), + bias: Math.random() + }; +} + +function train(model, data) { + // Dummy training: update weights based on data + // data: [{x, y}, ...] + let lr = 0.01; + for (const point of data) { + let pred = model.weight * point.x + model.bias; + let error = point.y - pred; + model.weight += lr * error * point.x; + model.bias += lr * error; + } + return model; +} + +function aggregate(globalModel, clientModel) { + // Simple averaging + return { + weight: (globalModel.weight + clientModel.weight) / 2, + bias: (globalModel.bias + clientModel.bias) / 2 + }; +} + +function update(localModel, globalModel) { + // Update local model with global model + localModel.weight = globalModel.weight; + localModel.bias = globalModel.bias; + return localModel; +} + +module.exports = { + createModel, + train, + aggregate, + update +}; diff --git a/federated/privacy.js b/federated/privacy.js new file mode 100644 index 00000000..3b83e54c --- /dev/null +++ b/federated/privacy.js @@ -0,0 +1,40 @@ +// Privacy-Preserving Mechanisms +// Differential privacy, secure aggregation + +function applyDifferentialPrivacy(model) { + // Add Gaussian noise to model weights for privacy + const noiseLevel = 0.01; + let noisyModel = {}; + for (const key in model) { + if (typeof model[key] === 'number') { + noisyModel[key] = model[key] + gaussianNoise(0, noiseLevel); + } else { + noisyModel[key] = model[key]; + } + } + return noisyModel; +} + +function gaussianNoise(mean, std) { + // Box-Muller transform + let u = 0, v = 0; + while(u === 0) u = Math.random(); + while(v === 0) v = Math.random(); + return std * Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v) + mean; +} + +function secureAggregate(models) { + // Simple averaging for demonstration + if (models.length === 0) return {}; + let agg = {}; + let keys = Object.keys(models[0]); + for (const key of keys) { + agg[key] = models.reduce((sum, m) => sum + (m[key] || 0), 0) / models.length; + } + return agg; +} + +module.exports = { + applyDifferentialPrivacy, + secureAggregate +}; diff --git a/federated/server.js b/federated/server.js new file mode 100644 index 00000000..d4feb176 --- /dev/null +++ b/federated/server.js @@ -0,0 +1,104 @@ +// Federated Learning Server +// Orchestrates rounds, aggregates models, manages clients + +const net = require('net'); +const crypto = require('./crypto'); +const privacy = require('./privacy'); +const model = require('./model'); +const compliance = require('./compliance'); +const config = require('./config'); + +class FederatedServer { + constructor(options) { + this.port = options.port || 9000; + this.clients = new Map(); // clientId -> socket + this.globalModel = model.createModel(); + this.round = 0; + this.auditLog = []; + this.server = net.createServer(this.handleConnection.bind(this)); + } + + start() { + this.server.listen(this.port, () => { + console.log(`Federated server listening on port ${this.port}`); + compliance.log('Server started', { port: this.port }); + }); + } + + handleConnection(socket) { + socket.on('data', (data) => this.handleClientMessage(socket, data)); + socket.on('end', () => this.handleClientDisconnect(socket)); + // Authentication handshake + crypto.authenticateClient(socket, (clientId) => { + this.clients.set(clientId, socket); + compliance.log('Client connected', { clientId }); + }); + } + + handleClientMessage(socket, data) { + // Decrypt and parse message + let message; + try { + message = crypto.decryptMessage(data); + } catch (e) { + compliance.log('Decryption failed', { error: e.message }); + return; + } + // Handle message types: model update, request global model, etc. + switch (message.type) { + case 'modelUpdate': + this.handleModelUpdate(socket, message); + break; + case 'requestGlobalModel': + this.sendGlobalModel(socket); + break; + default: + compliance.log('Unknown message type', { type: message.type }); + } + } + + handleModelUpdate(socket, message) { + // Apply privacy-preserving aggregation + const clientModel = privacy.applyDifferentialPrivacy(message.model); + this.aggregateModel(clientModel); + compliance.log('Model update received', { clientId: message.clientId }); + // Optionally send updated global model back + this.sendGlobalModel(socket); + } + + aggregateModel(clientModel) { + // Aggregate client model into global model + this.globalModel = model.aggregate(this.globalModel, clientModel); + this.round++; + compliance.log('Model aggregated', { round: this.round }); + } + + sendGlobalModel(socket) { + const encrypted = crypto.encryptMessage({ + type: 'globalModel', + model: this.globalModel, + round: this.round + }); + socket.write(encrypted); + compliance.log('Global model sent', {}); + } + + handleClientDisconnect(socket) { + // Remove client from map + for (const [clientId, s] of this.clients.entries()) { + if (s === socket) { + this.clients.delete(clientId); + compliance.log('Client disconnected', { clientId }); + break; + } + } + } +} + +module.exports = FederatedServer; + +// If run directly, start the server +if (require.main === module) { + const server = new FederatedServer({ port: config.serverPort }); + server.start(); +}