├── .prettierignore ├── .dockerignore ├── scripts └── start.sh ├── .prettierrc.json ├── src ├── config │ ├── server.js │ └── database.js ├── utils │ ├── EventEmitter.js │ ├── PasswordUtils.js │ ├── TokenUtils.js │ ├── MathUtils.js │ ├── DateUtils.js │ ├── ResponseFormatter.js │ ├── HashUtils.js │ ├── StringUtils.js │ ├── NumberUtils.js │ ├── RegexUtils.js │ ├── ColorUtils.js │ ├── URLUtils.js │ ├── GeoUtils.js │ ├── ObjectUtils.js │ ├── PaginationUtils.js │ ├── ArrayUtils.js │ ├── ConversionUtils.js │ ├── TimerUtils.js │ ├── SortUtils.js │ ├── FormatUtils.js │ ├── DeviceUtils.js │ ├── logger.js │ ├── FileManager.js │ ├── validators │ │ └── DataValidator.js │ ├── network │ │ └── NetworkUtils.js │ ├── CryptoUtils.js │ └── CircuitUtils.js ├── middleware │ ├── security.js │ ├── cors.js │ ├── compression.js │ ├── rateLimiter.js │ ├── validation │ │ └── userValidation.js │ ├── errorHandler.js │ └── auth.js ├── services │ ├── CacheService.js │ ├── blockchain │ │ └── ContractManager.js │ ├── KeyManager.js │ ├── analytics │ │ └── AnalyticsService.js │ ├── NotificationService.js │ ├── EncryptionService.js │ ├── IPFSService.js │ ├── ZKProofService.js │ └── BlockchainService.js ├── controllers │ ├── DataController.js │ └── UserController.js ├── routes │ ├── security.js │ ├── cache.js │ ├── utils.js │ ├── data.js │ ├── auth.js │ ├── contracts.js │ ├── files.js │ └── did.js ├── index.js ├── models │ ├── User.js │ └── Transaction.js ├── tests │ ├── services.test.js │ └── integration.test.js └── contracts │ └── ZeroDataToken.sol ├── Dockerfile.prod ├── .env.production ├── docs ├── guides │ └── development.md └── API.md ├── CHANGELOG.md ├── SECURITY.md ├── CONTRIBUTING.md ├── jest.config.js ├── Dockerfile ├── frontend └── package.json ├── tsconfig.json ├── LICENSE ├── .eslintrc.js ├── config └── database.js ├── docker-compose.yml ├── package.json ├── env.example ├── .github └── workflows │ └── ci-cd.yml ├── .gitignore ├── nginx.conf └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | *.log 3 | .DS_Store 4 | Thumbs.db 5 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | *.log 5 | .env* 6 | -------------------------------------------------------------------------------- /scripts/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Starting ZeroData API server..." 3 | npm start 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | {"prettier": true, "singleQuote": true, "trailingComma": "es5", "tabWidth": 2, "semi": false} 2 | -------------------------------------------------------------------------------- /src/config/server.js: -------------------------------------------------------------------------------- 1 | module.exports = { development: { host: "localhost", port: 3000 }, production: { host: "0.0.0.0", port: process.env.PORT || 3000 } }; 2 | -------------------------------------------------------------------------------- /src/utils/EventEmitter.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require("events"); class DataEventEmitter extends EventEmitter {} module.exports = new DataEventEmitter(); 2 | -------------------------------------------------------------------------------- /Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | WORKDIR /app 3 | COPY package*.json ./ 4 | RUN npm ci --only=production 5 | COPY . . 6 | EXPOSE 3000 7 | CMD ["npm", "start"] 8 | -------------------------------------------------------------------------------- /src/middleware/security.js: -------------------------------------------------------------------------------- 1 | const helmet = require("helmet"); module.exports = helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["self"], scriptSrc: ["self", "unsafe-inline"] } } }); 2 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | DATABASE_URL=mongodb://localhost:27017/zerodata 2 | REDIS_URL=redis://localhost:6379 3 | JWT_SECRET=your-jwt-secret-here 4 | ETHEREUM_RPC_URL=https://mainnet.infura.io/v3/YOUR_PROJECT_ID 5 | -------------------------------------------------------------------------------- /src/middleware/cors.js: -------------------------------------------------------------------------------- 1 | const cors = require("cors"); const corsOptions = { origin: process.env.ALLOWED_ORIGINS?.split(",") || ["http://localhost:3000"], credentials: true }; module.exports = cors(corsOptions); 2 | -------------------------------------------------------------------------------- /docs/guides/development.md: -------------------------------------------------------------------------------- 1 | # ZeroData Development Guide 2 | 3 | ## Getting Started 4 | 1. Clone the repository 5 | 2. Install dependencies 6 | 3. Configure environment variables 7 | 4. Start the development server 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.0.0] - 2024-12-20 4 | ### Added 5 | - Initial release of ZeroData platform 6 | - Zero-knowledge proof implementation 7 | - Decentralized data sharing 8 | - DAO governance system 9 | -------------------------------------------------------------------------------- /src/middleware/compression.js: -------------------------------------------------------------------------------- 1 | const compression = require("compression"); module.exports = compression({ filter: (req, res) => { if (req.headers["x-no-compression"]) return false; return compression.filter(req, res); } }); 2 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting Security Vulnerabilities 4 | 5 | Please report security vulnerabilities to security@zerodata.io 6 | 7 | ## Supported Versions 8 | - v1.x.x: Full security support 9 | - v0.x.x: Critical security patches only 10 | -------------------------------------------------------------------------------- /src/utils/PasswordUtils.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require("bcryptjs"); class PasswordUtils { static async hash(password) { return await bcrypt.hash(password, 12); } static async compare(password, hash) { return await bcrypt.compare(password, hash); } } module.exports = PasswordUtils; 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ZeroData 2 | 3 | ## How to Contribute 4 | 1. Fork the repository 5 | 2. Create a feature branch 6 | 3. Make your changes 7 | 4. Submit a pull request 8 | 9 | ## Code of Conduct 10 | Please be respectful and professional in all interactions. 11 | -------------------------------------------------------------------------------- /src/utils/TokenUtils.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); class TokenUtils { static generate(payload, expiresIn = "24h") { return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn }); } static verify(token) { return jwt.verify(token, process.env.JWT_SECRET); } } module.exports = TokenUtils; 2 | -------------------------------------------------------------------------------- /src/utils/MathUtils.js: -------------------------------------------------------------------------------- 1 | class MathUtils { static random(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } static clamp(value, min, max) { return Math.min(Math.max(value, min), max); } static percentage(value, total) { return (value / total) * 100; } } module.exports = MathUtils; 2 | -------------------------------------------------------------------------------- /src/utils/DateUtils.js: -------------------------------------------------------------------------------- 1 | const moment = require("moment"); class DateUtils { static formatDate(date, format = "YYYY-MM-DD") { return moment(date).format(format); } static isExpired(date) { return moment(date).isBefore(moment()); } static addDays(date, days) { return moment(date).add(days, "days").toDate(); } } module.exports = DateUtils; 2 | -------------------------------------------------------------------------------- /src/utils/ResponseFormatter.js: -------------------------------------------------------------------------------- 1 | class ResponseFormatter { static success(data, message = "Success") { return { success: true, message, data, timestamp: new Date().toISOString() }; } static error(message, code = 500) { return { success: false, error: message, code, timestamp: new Date().toISOString() }; } } module.exports = ResponseFormatter; 2 | -------------------------------------------------------------------------------- /src/services/CacheService.js: -------------------------------------------------------------------------------- 1 | class CacheService { static cache = new Map(); static set(key, value, ttl) { this.cache.set(key, { value, expires: Date.now() + ttl }); } static get(key) { const item = this.cache.get(key); if (!item || item.expires < Date.now()) { this.cache.delete(key); return null; } return item.value; } } module.exports = CacheService; 2 | -------------------------------------------------------------------------------- /src/utils/HashUtils.js: -------------------------------------------------------------------------------- 1 | const crypto = require("crypto"); class HashUtils { static sha256(data) { return crypto.createHash("sha256").update(data).digest("hex"); } static md5(data) { return crypto.createHash("md5").update(data).digest("hex"); } static randomBytes(size = 32) { return crypto.randomBytes(size).toString("hex"); } } module.exports = HashUtils; 2 | -------------------------------------------------------------------------------- /src/utils/StringUtils.js: -------------------------------------------------------------------------------- 1 | class StringUtils { static capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } static slugify(str) { return str.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""); } static truncate(str, length) { return str.length > length ? str.substring(0, length) + "..." : str; } } module.exports = StringUtils; 2 | -------------------------------------------------------------------------------- /src/utils/NumberUtils.js: -------------------------------------------------------------------------------- 1 | class NumberUtils { static formatCurrency(amount, currency = "USD") { return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount); } static formatNumber(num, decimals = 2) { return parseFloat(num).toFixed(decimals); } static isInteger(value) { return Number.isInteger(value); } } module.exports = NumberUtils; 2 | -------------------------------------------------------------------------------- /src/utils/RegexUtils.js: -------------------------------------------------------------------------------- 1 | class RegexUtils { static isEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } static isURL(url) { return /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/.test(url); } static isPhone(phone) { return /^\+?[1-9]\d{1,14}$/.test(phone); } } module.exports = RegexUtils; 2 | -------------------------------------------------------------------------------- /src/utils/ColorUtils.js: -------------------------------------------------------------------------------- 1 | class ColorUtils { static hexToRgb(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } static rgbToHex(r, g, b) { return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); } } module.exports = ColorUtils; 2 | -------------------------------------------------------------------------------- /src/config/database.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const connectDB = async () => { 4 | try { 5 | const conn = await mongoose.connect(process.env.DATABASE_URL); 6 | console.log(`MongoDB Connected: ${conn.connection.host}`); 7 | } catch (error) { 8 | console.error("Database connection error:", error); 9 | process.exit(1); 10 | } 11 | }; 12 | 13 | module.exports = connectDB; 14 | -------------------------------------------------------------------------------- /src/utils/URLUtils.js: -------------------------------------------------------------------------------- 1 | class URLUtils { static buildQuery(params) { return Object.keys(params).map(key => encodeURIComponent(key) + "=" + encodeURIComponent(params[key])).join("&"); } static parseQuery(queryString) { const params = {}; queryString.split("&").forEach(param => { const [key, value] = param.split("="); params[decodeURIComponent(key)] = decodeURIComponent(value); }); return params; } } module.exports = URLUtils; 2 | -------------------------------------------------------------------------------- /src/utils/GeoUtils.js: -------------------------------------------------------------------------------- 1 | class GeoUtils { static distance(lat1, lon1, lat2, lon2) { const R = 6371; const dLat = (lat2 - lat1) * Math.PI / 180; const dLon = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon/2) * Math.sin(dLon/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; } } module.exports = GeoUtils; 2 | -------------------------------------------------------------------------------- /src/utils/ObjectUtils.js: -------------------------------------------------------------------------------- 1 | class ObjectUtils { static deepClone(obj) { return JSON.parse(JSON.stringify(obj)); } static merge(target, source) { return { ...target, ...source }; } static pick(obj, keys) { const result = {}; keys.forEach(key => { if (key in obj) result[key] = obj[key]; }); return result; } static omit(obj, keys) { const result = { ...obj }; keys.forEach(key => delete result[key]); return result; } } module.exports = ObjectUtils; 2 | -------------------------------------------------------------------------------- /src/utils/PaginationUtils.js: -------------------------------------------------------------------------------- 1 | class PaginationUtils { static paginate(data, page = 1, limit = 10) { const offset = (page - 1) * limit; return { data: data.slice(offset, offset + limit), pagination: { page, limit, total: data.length, pages: Math.ceil(data.length / limit) } }; } static buildPaginationMeta(total, page, limit) { return { total, page, limit, pages: Math.ceil(total / limit), hasNext: page < Math.ceil(total / limit), hasPrev: page > 1 }; } } module.exports = PaginationUtils; 2 | -------------------------------------------------------------------------------- /src/utils/ArrayUtils.js: -------------------------------------------------------------------------------- 1 | class ArrayUtils { static unique(arr) { return [...new Set(arr)]; } static chunk(arr, size) { const chunks = []; for (let i = 0; i < arr.length; i += size) { chunks.push(arr.slice(i, i + size)); } return chunks; } static shuffle(arr) { const shuffled = [...arr]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; } } module.exports = ArrayUtils; 2 | -------------------------------------------------------------------------------- /src/utils/ConversionUtils.js: -------------------------------------------------------------------------------- 1 | class ConversionUtils { static bytesToHex(bytes) { return Array.from(bytes, byte => byte.toString(16).padStart(2, "0")).join(""); } static hexToBytes(hex) { const bytes = []; for (let i = 0; i < hex.length; i += 2) { bytes.push(parseInt(hex.substr(i, 2), 16)); } return new Uint8Array(bytes); } static base64Encode(str) { return Buffer.from(str).toString("base64"); } static base64Decode(str) { return Buffer.from(str, "base64").toString(); } } module.exports = ConversionUtils; 2 | -------------------------------------------------------------------------------- /src/utils/TimerUtils.js: -------------------------------------------------------------------------------- 1 | class TimerUtils { static delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } static timeout(promise, ms) { return Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), ms))]); } static debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } } module.exports = TimerUtils; 2 | -------------------------------------------------------------------------------- /src/middleware/rateLimiter.js: -------------------------------------------------------------------------------- 1 | const rateLimit = require("express-rate-limit"); 2 | 3 | const createLimiter = (windowMs, max, message) => { 4 | return rateLimit({ 5 | windowMs, 6 | max, 7 | message: { error: message }, 8 | standardHeaders: true, 9 | legacyHeaders: false 10 | }); 11 | }; 12 | 13 | const authLimiter = createLimiter(15 * 60 * 1000, 5, "Too many auth attempts"); 14 | const apiLimiter = createLimiter(15 * 60 * 1000, 100, "Too many API requests"); 15 | 16 | module.exports = { authLimiter, apiLimiter }; 17 | -------------------------------------------------------------------------------- /src/utils/SortUtils.js: -------------------------------------------------------------------------------- 1 | class SortUtils { static sortBy(array, key, direction = "asc") { return array.sort((a, b) => { const aVal = a[key]; const bVal = b[key]; if (direction === "asc") return aVal > bVal ? 1 : -1; return aVal < bVal ? 1 : -1; }); } static multiSort(array, sortKeys) { return array.sort((a, b) => { for (const { key, direction } of sortKeys) { const aVal = a[key]; const bVal = b[key]; if (aVal !== bVal) { return direction === "asc" ? (aVal > bVal ? 1 : -1) : (aVal < bVal ? 1 : -1); } } return 0; }); } } module.exports = SortUtils; 2 | -------------------------------------------------------------------------------- /src/utils/FormatUtils.js: -------------------------------------------------------------------------------- 1 | class FormatUtils { static fileSize(bytes) { const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; if (bytes === 0) return "0 Bytes"; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + " " + sizes[i]; } static duration(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; return `${hours}:${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`; } } module.exports = FormatUtils; 2 | -------------------------------------------------------------------------------- /src/middleware/validation/userValidation.js: -------------------------------------------------------------------------------- 1 | const { body, validationResult } = require("express-validator"); 2 | 3 | const validateUser = [ 4 | body("address").isEthereumAddress().withMessage("Invalid Ethereum address"), 5 | body("signature").isLength({ min: 132, max: 132 }).withMessage("Invalid signature"), 6 | (req, res, next) => { 7 | const errors = validationResult(req); 8 | if (!errors.isEmpty()) { 9 | return res.status(400).json({ errors: errors.array() }); 10 | } 11 | next(); 12 | } 13 | ]; 14 | 15 | module.exports = { validateUser }; 16 | -------------------------------------------------------------------------------- /src/utils/DeviceUtils.js: -------------------------------------------------------------------------------- 1 | class DeviceUtils { static isMobile(userAgent) { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent); } static getBrowser(userAgent) { if (userAgent.includes("Chrome")) return "Chrome"; if (userAgent.includes("Firefox")) return "Firefox"; if (userAgent.includes("Safari")) return "Safari"; return "Unknown"; } static getOS(userAgent) { if (userAgent.includes("Windows")) return "Windows"; if (userAgent.includes("Mac")) return "macOS"; if (userAgent.includes("Linux")) return "Linux"; return "Unknown"; } } module.exports = DeviceUtils; 2 | -------------------------------------------------------------------------------- /src/utils/logger.js: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | 3 | const createLogger = (service) => { 4 | return winston.createLogger({ 5 | level: "info", 6 | format: winston.format.combine( 7 | winston.format.timestamp(), 8 | winston.format.json() 9 | ), 10 | defaultMeta: { service }, 11 | transports: [ 12 | new winston.transports.File({ filename: "logs/error.log", level: "error" }), 13 | new winston.transports.File({ filename: "logs/combined.log" }), 14 | new winston.transports.Console({ format: winston.format.simple() }) 15 | ] 16 | }); 17 | }; 18 | 19 | module.exports = { createLogger }; 20 | -------------------------------------------------------------------------------- /src/services/blockchain/ContractManager.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("ethers"); 2 | 3 | class ContractManager { 4 | constructor(rpcUrl, privateKey) { 5 | this.provider = new ethers.JsonRpcProvider(rpcUrl); 6 | this.wallet = new ethers.Wallet(privateKey, this.provider); 7 | this.contracts = new Map(); 8 | } 9 | 10 | async loadContract(name, address, abi) { 11 | const contract = new ethers.Contract(address, abi, this.wallet); 12 | this.contracts.set(name, contract); 13 | return contract; 14 | } 15 | 16 | getContract(name) { 17 | return this.contracts.get(name); 18 | } 19 | } 20 | 21 | module.exports = ContractManager; 22 | -------------------------------------------------------------------------------- /src/services/KeyManager.js: -------------------------------------------------------------------------------- 1 | const crypto = require("crypto"); 2 | 3 | class KeyManager { 4 | static generateKeyPair() { 5 | return crypto.generateKeyPairSync("rsa", { 6 | modulusLength: 2048, 7 | publicKeyEncoding: { type: "spki", format: "pem" }, 8 | privateKeyEncoding: { type: "pkcs8", format: "pem" } 9 | }); 10 | } 11 | 12 | static encryptData(data, publicKey) { 13 | return crypto.publicEncrypt(publicKey, Buffer.from(data)).toString("base64"); 14 | } 15 | 16 | static decryptData(encryptedData, privateKey) { 17 | return crypto.privateDecrypt(privateKey, Buffer.from(encryptedData, "base64")).toString(); 18 | } 19 | } 20 | 21 | module.exports = KeyManager; 22 | -------------------------------------------------------------------------------- /src/utils/FileManager.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs").promises; 2 | const path = require("path"); 3 | 4 | class FileManager { 5 | static async saveFile(data, filename, directory = "uploads") { 6 | const filePath = path.join(directory, filename); 7 | await fs.mkdir(directory, { recursive: true }); 8 | await fs.writeFile(filePath, data); 9 | return filePath; 10 | } 11 | 12 | static async readFile(filePath) { 13 | return await fs.readFile(filePath); 14 | } 15 | 16 | static async deleteFile(filePath) { 17 | await fs.unlink(filePath); 18 | } 19 | 20 | static async listFiles(directory) { 21 | return await fs.readdir(directory); 22 | } 23 | } 24 | 25 | module.exports = FileManager; 26 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | collectCoverageFrom: [ 4 | 'src/**/*.{js,ts}', 5 | '!src/**/*.test.{js,ts}', 6 | '!src/**/*.spec.{js,ts}', 7 | '!src/tests/**' 8 | ], 9 | coverageDirectory: 'coverage', 10 | coverageReporters: ['text', 'lcov', 'html'], 11 | testMatch: [ 12 | '**/__tests__/**/*.{js,ts}', 13 | '**/?(*.)+(spec|test).{js,ts}' 14 | ], 15 | setupFilesAfterEnv: ['/src/tests/setup.js'], 16 | globalSetup: '/src/tests/globalSetup.js', 17 | globalTeardown: '/src/tests/globalTeardown.js', 18 | testTimeout: 30000, 19 | verbose: true, 20 | forceExit: true, 21 | clearMocks: true, 22 | resetMocks: true, 23 | restoreMocks: true 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils/validators/DataValidator.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("ethers"); 2 | 3 | class DataValidator { 4 | static validateEthereumAddress(address) { 5 | return /^0x[a-fA-F0-9]{40}$/.test(address); 6 | } 7 | 8 | static validateDataHash(hash) { 9 | return /^0x[a-fA-F0-9]{64}$/.test(hash); 10 | } 11 | 12 | static validateSignature(signature, message, address) { 13 | try { 14 | const recoveredAddress = ethers.verifyMessage(message, signature); 15 | return recoveredAddress.toLowerCase() === address.toLowerCase(); 16 | } catch { 17 | return false; 18 | } 19 | } 20 | 21 | static sanitizeInput(input) { 22 | return input.toString().replace(/[<>]/g, ""); 23 | } 24 | } 25 | 26 | module.exports = DataValidator; 27 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | # Set working directory 4 | WORKDIR /app 5 | 6 | # Copy package files 7 | COPY package*.json ./ 8 | 9 | # Install dependencies 10 | RUN npm ci --only=production 11 | 12 | # Copy source code 13 | COPY . . 14 | 15 | # Create uploads and logs directories 16 | RUN mkdir -p uploads logs 17 | 18 | # Create non-root user 19 | RUN addgroup -g 1001 -S nodejs 20 | RUN adduser -S zerodata -u 1001 21 | 22 | # Change ownership of app directory 23 | RUN chown -R zerodata:nodejs /app 24 | 25 | # Switch to non-root user 26 | USER zerodata 27 | 28 | # Expose port 29 | EXPOSE 3000 30 | 31 | # Health check 32 | HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ 33 | CMD curl -f http://localhost:3000/health || exit 1 34 | 35 | # Start application 36 | CMD ["npm", "start"] 37 | -------------------------------------------------------------------------------- /src/services/analytics/AnalyticsService.js: -------------------------------------------------------------------------------- 1 | class AnalyticsService { 2 | static async trackEvent(eventType, userId, metadata = {}) { 3 | const event = { 4 | type: eventType, 5 | userId, 6 | metadata, 7 | timestamp: new Date(), 8 | ip: metadata.ip || "unknown" 9 | }; 10 | 11 | // Log event for now, could save to database 12 | console.log("Analytics Event:", event); 13 | return event; 14 | } 15 | 16 | static async getUserStats(userId) { 17 | // Mock implementation 18 | return { 19 | totalUploads: 5, 20 | totalDownloads: 12, 21 | totalEarnings: "1500000000000000000", 22 | reputationScore: 85 23 | }; 24 | } 25 | 26 | static async getSystemStats() { 27 | return { 28 | totalUsers: 1250, 29 | totalData: 5600, 30 | totalTransactions: 12500, 31 | dailyActiveUsers: 340 32 | }; 33 | } 34 | } 35 | 36 | module.exports = AnalyticsService; 37 | -------------------------------------------------------------------------------- /src/utils/network/NetworkUtils.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | class NetworkUtils { 4 | static async checkNetworkStatus(rpcUrl) { 5 | try { 6 | const response = await axios.post(rpcUrl, { 7 | jsonrpc: "2.0", 8 | method: "eth_blockNumber", 9 | params: [], 10 | id: 1 11 | }); 12 | return { status: "online", blockNumber: response.data.result }; 13 | } catch (error) { 14 | return { status: "offline", error: error.message }; 15 | } 16 | } 17 | 18 | static async getGasPrice(rpcUrl) { 19 | try { 20 | const response = await axios.post(rpcUrl, { 21 | jsonrpc: "2.0", 22 | method: "eth_gasPrice", 23 | params: [], 24 | id: 1 25 | }); 26 | return response.data.result; 27 | } catch (error) { 28 | throw new Error(`Failed to get gas price: ${error.message}`); 29 | } 30 | } 31 | } 32 | 33 | module.exports = NetworkUtils; 34 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zerodata-web", 3 | "version": "1.0.0", 4 | "description": "ZeroData Web Frontend", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "next dev", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "next lint", 11 | "test": "jest", 12 | "test:watch": "jest --watch" 13 | }, 14 | "dependencies": { 15 | "next": "^13.5.0", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0", 18 | "ethers": "^6.7.1", 19 | "wagmi": "^1.4.0", 20 | "@rainbow-me/rainbowkit": "^1.3.0", 21 | "axios": "^1.5.0", 22 | "tailwindcss": "^3.3.0", 23 | "lucide-react": "^0.263.0" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^20.5.9", 27 | "@types/react": "^18.2.0", 28 | "@types/react-dom": "^18.2.0", 29 | "typescript": "^5.2.2", 30 | "eslint": "^8.48.0", 31 | "eslint-config-next": "^13.5.0", 32 | "jest": "^29.6.4", 33 | "@testing-library/react": "^13.4.0", 34 | "@testing-library/jest-dom": "^5.17.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "lib": ["ES2020"], 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "sourceMap": true, 16 | "removeComments": true, 17 | "noImplicitAny": true, 18 | "noImplicitReturns": true, 19 | "noImplicitThis": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "exactOptionalPropertyTypes": true, 23 | "noImplicitOverride": true, 24 | "noPropertyAccessFromIndexSignature": true, 25 | "noUncheckedIndexedAccess": true 26 | }, 27 | "include": [ 28 | "src/**/*" 29 | ], 30 | "exclude": [ 31 | "node_modules", 32 | "dist", 33 | "**/*.test.ts", 34 | "**/*.spec.ts" 35 | ], 36 | "ts-node": { 37 | "esm": true, 38 | "experimentalSpecifierResolution": "node" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/controllers/DataController.js: -------------------------------------------------------------------------------- 1 | const DataEntry = require("../models/DataEntry"); 2 | 3 | class DataController { 4 | static async uploadData(req, res) { 5 | try { 6 | const { title, description, category, price } = req.body; 7 | const dataEntry = new DataEntry({ 8 | title, 9 | description, 10 | category, 11 | price, 12 | owner: req.user.id, 13 | ownerAddress: req.user.address 14 | }); 15 | 16 | await dataEntry.save(); 17 | res.status(201).json({ message: "Data uploaded successfully", data: dataEntry }); 18 | } catch (error) { 19 | res.status(500).json({ error: error.message }); 20 | } 21 | } 22 | 23 | static async getData(req, res) { 24 | try { 25 | const data = await DataEntry.findById(req.params.id).populate("owner"); 26 | if (!data) { 27 | return res.status(404).json({ error: "Data not found" }); 28 | } 29 | res.json(data); 30 | } catch (error) { 31 | res.status(500).json({ error: error.message }); 32 | } 33 | } 34 | } 35 | 36 | module.exports = DataController; 37 | -------------------------------------------------------------------------------- /src/controllers/UserController.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/User"); 2 | const { validationResult } = require("express-validator"); 3 | 4 | class UserController { 5 | static async createUser(req, res) { 6 | try { 7 | const errors = validationResult(req); 8 | if (!errors.isEmpty()) { 9 | return res.status(400).json({ errors: errors.array() }); 10 | } 11 | 12 | const { address, username, email } = req.body; 13 | const user = new User({ address, username, email }); 14 | await user.save(); 15 | 16 | res.status(201).json({ message: "User created successfully", user }); 17 | } catch (error) { 18 | res.status(500).json({ error: error.message }); 19 | } 20 | } 21 | 22 | static async getUser(req, res) { 23 | try { 24 | const user = await User.findById(req.params.id); 25 | if (!user) { 26 | return res.status(404).json({ error: "User not found" }); 27 | } 28 | res.json(user); 29 | } catch (error) { 30 | res.status(500).json({ error: error.message }); 31 | } 32 | } 33 | } 34 | 35 | module.exports = UserController; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ZeroData Protocol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/services/NotificationService.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require("nodemailer"); 2 | 3 | class NotificationService { 4 | constructor() { 5 | this.transporter = nodemailer.createTransporter({ 6 | host: process.env.SMTP_HOST, 7 | port: process.env.SMTP_PORT, 8 | secure: false, 9 | auth: { 10 | user: process.env.SMTP_USER, 11 | pass: process.env.SMTP_PASS 12 | } 13 | }); 14 | } 15 | 16 | async sendEmail(to, subject, text, html) { 17 | try { 18 | const info = await this.transporter.sendMail({ 19 | from: process.env.FROM_EMAIL, 20 | to, 21 | subject, 22 | text, 23 | html 24 | }); 25 | return { success: true, messageId: info.messageId }; 26 | } catch (error) { 27 | console.error("Email send error:", error); 28 | return { success: false, error: error.message }; 29 | } 30 | } 31 | 32 | async sendWelcomeEmail(userEmail, userName) { 33 | const subject = "Welcome to ZeroData!"; 34 | const text = `Welcome ${userName}! Thank you for joining ZeroData.`; 35 | return await this.sendEmail(userEmail, subject, text); 36 | } 37 | } 38 | 39 | module.exports = NotificationService; 40 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | es2021: true, 6 | jest: true 7 | }, 8 | extends: [ 9 | 'eslint:recommended', 10 | '@typescript-eslint/recommended' 11 | ], 12 | parser: '@typescript-eslint/parser', 13 | parserOptions: { 14 | ecmaVersion: 2021, 15 | sourceType: 'module' 16 | }, 17 | plugins: [ 18 | '@typescript-eslint', 19 | 'security' 20 | ], 21 | rules: { 22 | 'no-console': 'warn', 23 | 'no-unused-vars': 'error', 24 | 'prefer-const': 'error', 25 | 'no-var': 'error', 26 | 'object-shorthand': 'error', 27 | 'prefer-arrow-callback': 'error', 28 | 'prefer-template': 'error', 29 | 'template-curly-spacing': 'error', 30 | 'arrow-spacing': 'error', 31 | 'comma-dangle': ['error', 'never'], 32 | 'quotes': ['error', 'single'], 33 | 'semi': ['error', 'always'], 34 | 'indent': ['error', 2], 35 | 'max-len': ['error', { code: 100 }], 36 | 'security/detect-object-injection': 'error', 37 | 'security/detect-non-literal-regexp': 'error', 38 | 'security/detect-unsafe-regex': 'error' 39 | }, 40 | ignorePatterns: [ 41 | 'node_modules/', 42 | 'dist/', 43 | 'coverage/', 44 | 'build/' 45 | ] 46 | }; 47 | -------------------------------------------------------------------------------- /config/database.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | username: process.env.DB_USERNAME || 'zerodata', 4 | password: process.env.DB_PASSWORD || 'password', 5 | database: process.env.DB_NAME || 'zerodata_dev', 6 | host: process.env.DB_HOST || 'localhost', 7 | port: process.env.DB_PORT || 5432, 8 | dialect: 'postgres', 9 | logging: console.log, 10 | pool: { 11 | max: 5, 12 | min: 0, 13 | acquire: 30000, 14 | idle: 10000 15 | } 16 | }, 17 | test: { 18 | username: process.env.DB_USERNAME || 'zerodata', 19 | password: process.env.DB_PASSWORD || 'password', 20 | database: process.env.DB_NAME || 'zerodata_test', 21 | host: process.env.DB_HOST || 'localhost', 22 | port: process.env.DB_PORT || 5432, 23 | dialect: 'postgres', 24 | logging: false, 25 | pool: { 26 | max: 5, 27 | min: 0, 28 | acquire: 30000, 29 | idle: 10000 30 | } 31 | }, 32 | production: { 33 | username: process.env.DB_USERNAME, 34 | password: process.env.DB_PASSWORD, 35 | database: process.env.DB_NAME, 36 | host: process.env.DB_HOST, 37 | port: process.env.DB_PORT || 5432, 38 | dialect: 'postgres', 39 | logging: false, 40 | pool: { 41 | max: 20, 42 | min: 5, 43 | acquire: 60000, 44 | idle: 10000 45 | }, 46 | dialectOptions: { 47 | ssl: { 48 | require: true, 49 | rejectUnauthorized: false 50 | } 51 | } 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | api: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | ports: 9 | - "3000:3000" 10 | environment: 11 | - NODE_ENV=production 12 | - PORT=3000 13 | - DATABASE_URL=mongodb://mongo:27017/zerodata 14 | - REDIS_URL=redis://redis:6379 15 | - ETHEREUM_RPC_URL=${ETHEREUM_RPC_URL} 16 | - IPFS_API_URL=http://ipfs:5001 17 | depends_on: 18 | - mongo 19 | - redis 20 | - ipfs 21 | volumes: 22 | - ./uploads:/app/uploads 23 | - ./logs:/app/logs 24 | 25 | mongo: 26 | image: mongo:6.0 27 | ports: 28 | - "27017:27017" 29 | volumes: 30 | - mongo_data:/data/db 31 | environment: 32 | - MONGO_INITDB_ROOT_USERNAME=admin 33 | - MONGO_INITDB_ROOT_PASSWORD=password 34 | 35 | redis: 36 | image: redis:7-alpine 37 | ports: 38 | - "6379:6379" 39 | volumes: 40 | - redis_data:/data 41 | 42 | ipfs: 43 | image: ipfs/go-ipfs:latest 44 | ports: 45 | - "4001:4001" 46 | - "5001:5001" 47 | - "8080:8080" 48 | volumes: 49 | - ipfs_data:/data/ipfs 50 | environment: 51 | - IPFS_PROFILE=server 52 | 53 | nginx: 54 | image: nginx:alpine 55 | ports: 56 | - "80:80" 57 | - "443:443" 58 | volumes: 59 | - ./nginx.conf:/etc/nginx/nginx.conf 60 | - ./ssl:/etc/nginx/ssl 61 | depends_on: 62 | - api 63 | 64 | volumes: 65 | mongo_data: 66 | redis_data: 67 | ipfs_data: 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zerodata", 3 | "version": "0.1.0", 4 | "description": "A decentralized data-sharing network powered by zero-knowledge proofs", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node src/index.js", 8 | "dev": "nodemon src/index.js", 9 | "test": "jest", 10 | "build": "webpack --mode production", 11 | "lint": "eslint src/", 12 | "format": "prettier --write src/" 13 | }, 14 | "keywords": [ 15 | "blockchain", 16 | "zero-knowledge-proofs", 17 | "decentralized", 18 | "data-sharing", 19 | "privacy", 20 | "ethereum", 21 | "ipfs" 22 | ], 23 | "author": "ZeroData Team", 24 | "license": "MIT", 25 | "dependencies": { 26 | "express": "^4.18.2", 27 | "cors": "^2.8.5", 28 | "helmet": "^7.0.0", 29 | "dotenv": "^16.3.1", 30 | "ethers": "^6.7.1", 31 | "ipfs-http-client": "^60.0.1", 32 | "snarkjs": "^0.7.2", 33 | "circomlib": "^2.0.5", 34 | "circomlibjs": "^0.1.7", 35 | "web3": "^4.1.1", 36 | "axios": "^1.5.0", 37 | "multer": "^1.4.5-lts.1", 38 | "crypto": "^1.0.1", 39 | "jsonwebtoken": "^9.0.2", 40 | "bcryptjs": "^2.4.3", 41 | "mongoose": "^7.5.0", 42 | "redis": "^4.6.7", 43 | "winston": "^3.10.0" 44 | }, 45 | "devDependencies": { 46 | "nodemon": "^3.0.1", 47 | "jest": "^29.6.4", 48 | "supertest": "^6.3.3", 49 | "eslint": "^8.48.0", 50 | "prettier": "^3.0.3", 51 | "webpack": "^5.88.2", 52 | "webpack-cli": "^5.1.4", 53 | "@types/node": "^20.5.9", 54 | "typescript": "^5.2.2" 55 | }, 56 | "engines": { 57 | "node": ">=16.0.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/middleware/errorHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global error handling middleware 3 | */ 4 | const errorHandler = (err, req, res, next) => { 5 | // Log error 6 | console.error('Error:', err); 7 | 8 | // Default error 9 | let error = { 10 | message: err.message || 'Internal Server Error', 11 | status: err.status || 500 12 | }; 13 | 14 | // Mongoose validation error 15 | if (err.name === 'ValidationError') { 16 | const messages = Object.values(err.errors).map(val => val.message); 17 | error = { 18 | message: 'Validation Error', 19 | status: 400, 20 | details: messages 21 | }; 22 | } 23 | 24 | // Mongoose duplicate key error 25 | if (err.code === 11000) { 26 | error = { 27 | message: 'Duplicate field value', 28 | status: 400 29 | }; 30 | } 31 | 32 | // JWT errors 33 | if (err.name === 'JsonWebTokenError') { 34 | error = { 35 | message: 'Invalid token', 36 | status: 401 37 | }; 38 | } 39 | 40 | if (err.name === 'TokenExpiredError') { 41 | error = { 42 | message: 'Token expired', 43 | status: 401 44 | }; 45 | } 46 | 47 | // Cast error (invalid ObjectId) 48 | if (err.name === 'CastError') { 49 | error = { 50 | message: 'Invalid ID format', 51 | status: 400 52 | }; 53 | } 54 | 55 | // Send error response 56 | res.status(error.status).json({ 57 | success: false, 58 | error: error.message, 59 | ...(process.env.NODE_ENV === 'development' && { stack: err.stack }), 60 | ...(error.details && { details: error.details }) 61 | }); 62 | }; 63 | 64 | module.exports = errorHandler; 65 | -------------------------------------------------------------------------------- /env.example: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | NODE_ENV=development 3 | 4 | # Ethereum Configuration 5 | ETHEREUM_RPC_URL=https://goerli.infura.io/v3/YOUR_PROJECT_ID 6 | PRIVATE_KEY=0x1234567890123456789012345678901234567890123456789012345678901234 7 | 8 | # IPFS Configuration 9 | IPFS_API_URL=http://localhost:5001 10 | IPFS_GATEWAY_URL=https://ipfs.io/ipfs/ 11 | 12 | # Database Configuration 13 | DATABASE_URL=mongodb://localhost:27017/zerodata 14 | REDIS_URL=redis://localhost:6379 15 | 16 | # JWT Configuration 17 | JWT_SECRET=your-super-secret-jwt-key-change-this-in-production 18 | JWT_EXPIRES_IN=24h 19 | 20 | # Admin Configuration 21 | ADMIN_ADDRESSES=0x1234567890123456789012345678901234567890,0xabcdef1234567890abcdef1234567890abcdef12 22 | 23 | # CORS Configuration 24 | ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001,https://zerodata.io 25 | 26 | # File Upload Configuration 27 | MAX_FILE_SIZE=10485760 28 | UPLOAD_PATH=./uploads 29 | 30 | # Logging Configuration 31 | LOG_LEVEL=info 32 | LOG_FILE=./logs/app.log 33 | 34 | # Zero Knowledge Proof Configuration 35 | CIRCUITS_PATH=./src/circuits 36 | PROOFS_PATH=./src/proofs 37 | WITNESSES_PATH=./src/witnesses 38 | 39 | # Smart Contract Configuration 40 | DATA_REGISTRY_ADDRESS=0x0000000000000000000000000000000000000000 41 | ZERO_DATA_TOKEN_ADDRESS=0x0000000000000000000000000000000000000000 42 | 43 | # Rate Limiting 44 | RATE_LIMIT_WINDOW_MS=900000 45 | RATE_LIMIT_MAX_REQUESTS=100 46 | 47 | # Security Configuration 48 | BCRYPT_ROUNDS=12 49 | SESSION_SECRET=your-session-secret-key 50 | 51 | # Monitoring Configuration 52 | ENABLE_METRICS=true 53 | METRICS_PORT=9090 54 | 55 | # Development Configuration 56 | ENABLE_DEBUG=true 57 | DEBUG_LEVEL=verbose 58 | -------------------------------------------------------------------------------- /.github/workflows/ci-cd.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD Pipeline 2 | 3 | on: 4 | push: 5 | branches: [ main, develop ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [16.x, 18.x, 20.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | cache: 'npm' 25 | 26 | - name: Install dependencies 27 | run: npm ci 28 | 29 | - name: Run linter 30 | run: npm run lint 31 | 32 | - name: Run tests 33 | run: npm test 34 | 35 | - name: Run security audit 36 | run: npm audit --audit-level=high 37 | 38 | build: 39 | needs: test 40 | runs-on: ubuntu-latest 41 | 42 | steps: 43 | - uses: actions/checkout@v3 44 | 45 | - name: Set up Docker Buildx 46 | uses: docker/setup-buildx-action@v2 47 | 48 | - name: Login to Docker Hub 49 | uses: docker/login-action@v2 50 | with: 51 | username: ${{ secrets.DOCKER_USERNAME }} 52 | password: ${{ secrets.DOCKER_PASSWORD }} 53 | 54 | - name: Build and push Docker image 55 | uses: docker/build-push-action@v4 56 | with: 57 | context: . 58 | push: true 59 | tags: | 60 | zerodata/api:latest 61 | zerodata/api:${{ github.sha }} 62 | 63 | deploy: 64 | needs: build 65 | runs-on: ubuntu-latest 66 | if: github.ref == 'refs/heads/main' 67 | 68 | steps: 69 | - uses: actions/checkout@v3 70 | 71 | - name: Deploy to production 72 | run: | 73 | echo "Deploying to production..." 74 | # Add deployment commands here 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | __pycache__/ 4 | *.pyc 5 | *.pyo 6 | *.pyd 7 | .Python 8 | env/ 9 | venv/ 10 | .env 11 | .env.local 12 | .env.development.local 13 | .env.test.local 14 | .env.production.local 15 | 16 | # Build outputs 17 | dist/ 18 | build/ 19 | *.egg-info/ 20 | target/ 21 | *.jar 22 | *.war 23 | 24 | # IDE files 25 | .vscode/ 26 | .idea/ 27 | *.swp 28 | *.swo 29 | *~ 30 | 31 | # OS files 32 | .DS_Store 33 | Thumbs.db 34 | 35 | # Logs 36 | logs/ 37 | *.log 38 | npm-debug.log* 39 | yarn-debug.log* 40 | yarn-error.log* 41 | 42 | # Runtime data 43 | pids/ 44 | *.pid 45 | *.seed 46 | *.pid.lock 47 | 48 | # Coverage directory used by tools like istanbul 49 | coverage/ 50 | *.lcov 51 | 52 | # nyc test coverage 53 | .nyc_output 54 | 55 | # Dependency directories 56 | jspm_packages/ 57 | 58 | # Optional npm cache directory 59 | .npm 60 | 61 | # Optional eslint cache 62 | .eslintcache 63 | 64 | # Microbundle cache 65 | .rpt2_cache/ 66 | .rts2_cache_cjs/ 67 | .rts2_cache_es/ 68 | .rts2_cache_umd/ 69 | 70 | # Optional REPL history 71 | .node_repl_history 72 | 73 | # Output of 'npm pack' 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | .yarn-integrity 78 | 79 | # parcel-bundler cache (https://parceljs.org/) 80 | .cache 81 | .parcel-cache 82 | 83 | # Next.js build output 84 | .next 85 | 86 | # Nuxt.js build / generate output 87 | .nuxt 88 | 89 | # Gatsby files 90 | .cache/ 91 | public 92 | 93 | # Storybook build outputs 94 | .out 95 | .storybook-out 96 | 97 | # Temporary folders 98 | tmp/ 99 | temp/ 100 | 101 | # Sensitive files - DO NOT COMMIT 102 | github_accounts.csv 103 | project_description.txt 104 | *.key 105 | *.pem 106 | secrets/ 107 | config/secrets.json 108 | 109 | # Blockchain specific 110 | .ethereum/ 111 | .ganache/ 112 | hardhat.config.js.local 113 | 114 | # Zero Knowledge Proof artifacts 115 | circuits/ 116 | proofs/ 117 | witnesses/ 118 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | events { 2 | worker_connections 1024; 3 | } 4 | 5 | http { 6 | upstream api { 7 | server api:3000; 8 | } 9 | 10 | server { 11 | listen 80; 12 | server_name localhost; 13 | 14 | # Redirect HTTP to HTTPS 15 | return 301 https://$server_name$request_uri; 16 | } 17 | 18 | server { 19 | listen 443 ssl http2; 20 | server_name localhost; 21 | 22 | # SSL configuration 23 | ssl_certificate /etc/nginx/ssl/cert.pem; 24 | ssl_certificate_key /etc/nginx/ssl/key.pem; 25 | ssl_protocols TLSv1.2 TLSv1.3; 26 | ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; 27 | ssl_prefer_server_ciphers off; 28 | 29 | # Security headers 30 | add_header X-Frame-Options DENY; 31 | add_header X-Content-Type-Options nosniff; 32 | add_header X-XSS-Protection "1; mode=block"; 33 | add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; 34 | 35 | # Rate limiting 36 | limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; 37 | limit_req zone=api burst=20 nodelay; 38 | 39 | # Proxy configuration 40 | location / { 41 | proxy_pass http://api; 42 | proxy_set_header Host $host; 43 | proxy_set_header X-Real-IP $remote_addr; 44 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 45 | proxy_set_header X-Forwarded-Proto $scheme; 46 | proxy_connect_timeout 30s; 47 | proxy_send_timeout 30s; 48 | proxy_read_timeout 30s; 49 | } 50 | 51 | # Static files 52 | location /static/ { 53 | alias /app/static/; 54 | expires 1y; 55 | add_header Cache-Control "public, immutable"; 56 | } 57 | 58 | # Health check 59 | location /health { 60 | proxy_pass http://api/health; 61 | access_log off; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/routes/security.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const helmet = require('helmet'); 3 | const rateLimit = require('express-rate-limit'); 4 | 5 | const router = express.Router(); 6 | 7 | // Security middleware 8 | router.use(helmet({ 9 | contentSecurityPolicy: { 10 | directives: { 11 | defaultSrc: ["'self'"], 12 | styleSrc: ["'self'", "'unsafe-inline'"], 13 | scriptSrc: ["'self'"], 14 | imgSrc: ["'self'", "data:", "https:"], 15 | connectSrc: ["'self'"], 16 | fontSrc: ["'self'"], 17 | objectSrc: ["'none'"], 18 | mediaSrc: ["'self'"], 19 | frameSrc: ["'none'"], 20 | }, 21 | }, 22 | crossOriginEmbedderPolicy: false 23 | })); 24 | 25 | // Rate limiting 26 | const limiter = rateLimit({ 27 | windowMs: 15 * 60 * 1000, // 15 minutes 28 | max: 100, // limit each IP to 100 requests per windowMs 29 | message: { 30 | error: 'Too many requests from this IP, please try again later.' 31 | }, 32 | standardHeaders: true, 33 | legacyHeaders: false, 34 | }); 35 | 36 | router.use(limiter); 37 | 38 | /** 39 | * GET /api/security/headers 40 | * Get security headers information 41 | */ 42 | router.get('/headers', (req, res) => { 43 | res.json({ 44 | message: 'Security headers applied', 45 | headers: { 46 | 'X-Frame-Options': 'DENY', 47 | 'X-Content-Type-Options': 'nosniff', 48 | 'X-XSS-Protection': '1; mode=block', 49 | 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', 50 | 'Content-Security-Policy': 'default-src \'self\'' 51 | } 52 | }); 53 | }); 54 | 55 | /** 56 | * POST /api/security/validate-input 57 | * Validate user input for security 58 | */ 59 | router.post('/validate-input', (req, res) => { 60 | try { 61 | const { input, type } = req.body; 62 | 63 | if (!input || !type) { 64 | return res.status(400).json({ 65 | error: 'Input and type are required' 66 | }); 67 | } 68 | 69 | let isValid = false; 70 | let sanitized = input; 71 | 72 | switch (type) { 73 | case 'email': 74 | isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input); 75 | break; 76 | case 'address': 77 | isValid = /^0x[a-fA-F0-9]{40}$/.test(input); 78 | break; 79 | case 'hash': 80 | isValid = /^0x[a-fA-F0-9]{64}$/.test(input); 81 | break; 82 | case 'string': 83 | isValid = typeof input === 'string' && input.length > 0 && input.length < 1000; 84 | sanitized = input.replace(/)<[^<]*)*<\/script>/gi, ''); 85 | break; 86 | default: 87 | isValid = false; 88 | } 89 | 90 | res.json({ 91 | input: input, 92 | type: type, 93 | valid: isValid, 94 | sanitized: sanitized 95 | }); 96 | } catch (error) { 97 | res.status(500).json({ 98 | error: 'Input validation failed', 99 | message: error.message 100 | }); 101 | } 102 | }); 103 | 104 | module.exports = router; 105 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const helmet = require('helmet'); 4 | const dotenv = require('dotenv'); 5 | const winston = require('winston'); 6 | 7 | // Load environment variables 8 | dotenv.config(); 9 | 10 | // Import routes 11 | const dataRoutes = require('./routes/data'); 12 | const authRoutes = require('./routes/auth'); 13 | const contractRoutes = require('./routes/contracts'); 14 | const marketRoutes = require('./routes/market'); 15 | const incentivesRoutes = require('./routes/incentives'); 16 | const didRoutes = require('./routes/did'); 17 | const chainsRoutes = require('./routes/chains'); 18 | const daoRoutes = require('./routes/dao'); 19 | 20 | // Import middleware 21 | const errorHandler = require('./middleware/errorHandler'); 22 | const authMiddleware = require('./middleware/auth'); 23 | 24 | // Initialize logger 25 | const logger = winston.createLogger({ 26 | level: 'info', 27 | format: winston.format.combine( 28 | winston.format.timestamp(), 29 | winston.format.errors({ stack: true }), 30 | winston.format.json() 31 | ), 32 | transports: [ 33 | new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), 34 | new winston.transports.File({ filename: 'logs/combined.log' }), 35 | new winston.transports.Console({ 36 | format: winston.format.simple() 37 | }) 38 | ] 39 | }); 40 | 41 | const app = express(); 42 | const PORT = process.env.PORT || 3000; 43 | 44 | // Security middleware 45 | app.use(helmet()); 46 | app.use(cors({ 47 | origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'], 48 | credentials: true 49 | })); 50 | 51 | // Body parsing middleware 52 | app.use(express.json({ limit: '10mb' })); 53 | app.use(express.urlencoded({ extended: true, limit: '10mb' })); 54 | 55 | // Request logging 56 | app.use((req, res, next) => { 57 | logger.info(`${req.method} ${req.path}`, { 58 | ip: req.ip, 59 | userAgent: req.get('User-Agent'), 60 | timestamp: new Date().toISOString() 61 | }); 62 | next(); 63 | }); 64 | 65 | // Health check endpoint 66 | app.get('/health', (req, res) => { 67 | res.status(200).json({ 68 | status: 'healthy', 69 | timestamp: new Date().toISOString(), 70 | version: process.env.npm_package_version || '0.1.0' 71 | }); 72 | }); 73 | 74 | // API routes 75 | app.use('/api/auth', authRoutes); 76 | app.use('/api/data', authMiddleware, dataRoutes); 77 | app.use('/api/contracts', authMiddleware, contractRoutes); 78 | app.use('/api/market', authMiddleware, marketRoutes); 79 | app.use('/api/incentives', authMiddleware, incentivesRoutes); 80 | app.use('/api/did', authMiddleware, didRoutes); 81 | app.use('/api/chains', authMiddleware, chainsRoutes); 82 | app.use('/api/dao', authMiddleware, daoRoutes); 83 | 84 | // Error handling middleware 85 | app.use(errorHandler); 86 | 87 | // 404 handler 88 | app.use('*', (req, res) => { 89 | res.status(404).json({ 90 | error: 'Route not found', 91 | path: req.originalUrl, 92 | method: req.method 93 | }); 94 | }); 95 | 96 | // Start server 97 | app.listen(PORT, () => { 98 | logger.info(`ZeroData API server running on port ${PORT}`); 99 | logger.info(`Environment: ${process.env.NODE_ENV || 'development'}`); 100 | }); 101 | 102 | module.exports = app; 103 | -------------------------------------------------------------------------------- /src/routes/cache.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const redis = require('redis'); 3 | 4 | const router = express.Router(); 5 | 6 | // Redis client configuration 7 | const redisClient = redis.createClient({ 8 | url: process.env.REDIS_URL || 'redis://localhost:6379' 9 | }); 10 | 11 | redisClient.on('error', (err) => { 12 | console.error('Redis Client Error:', err); 13 | }); 14 | 15 | redisClient.on('connect', () => { 16 | console.log('Connected to Redis'); 17 | }); 18 | 19 | // Connect to Redis 20 | redisClient.connect(); 21 | 22 | /** 23 | * GET /api/cache/:key 24 | * Get value from cache 25 | */ 26 | router.get('/:key', async (req, res) => { 27 | try { 28 | const { key } = req.params; 29 | const value = await redisClient.get(key); 30 | 31 | if (value === null) { 32 | return res.status(404).json({ 33 | error: 'Key not found in cache' 34 | }); 35 | } 36 | 37 | res.json({ 38 | key: key, 39 | value: value, 40 | cached: true 41 | }); 42 | } catch (error) { 43 | res.status(500).json({ 44 | error: 'Failed to get value from cache', 45 | message: error.message 46 | }); 47 | } 48 | }); 49 | 50 | /** 51 | * POST /api/cache/:key 52 | * Set value in cache 53 | */ 54 | router.post('/:key', async (req, res) => { 55 | try { 56 | const { key } = req.params; 57 | const { value, ttl } = req.body; 58 | 59 | if (!value) { 60 | return res.status(400).json({ 61 | error: 'Value is required' 62 | }); 63 | } 64 | 65 | if (ttl) { 66 | await redisClient.setEx(key, ttl, value); 67 | } else { 68 | await redisClient.set(key, value); 69 | } 70 | 71 | res.status(201).json({ 72 | message: 'Value cached successfully', 73 | key: key, 74 | ttl: ttl || 'no expiration' 75 | }); 76 | } catch (error) { 77 | res.status(500).json({ 78 | error: 'Failed to cache value', 79 | message: error.message 80 | }); 81 | } 82 | }); 83 | 84 | /** 85 | * DELETE /api/cache/:key 86 | * Delete value from cache 87 | */ 88 | router.delete('/:key', async (req, res) => { 89 | try { 90 | const { key } = req.params; 91 | const deleted = await redisClient.del(key); 92 | 93 | if (deleted === 0) { 94 | return res.status(404).json({ 95 | error: 'Key not found in cache' 96 | }); 97 | } 98 | 99 | res.json({ 100 | message: 'Value deleted from cache', 101 | key: key 102 | }); 103 | } catch (error) { 104 | res.status(500).json({ 105 | error: 'Failed to delete value from cache', 106 | message: error.message 107 | }); 108 | } 109 | }); 110 | 111 | /** 112 | * GET /api/cache/stats 113 | * Get cache statistics 114 | */ 115 | router.get('/stats', async (req, res) => { 116 | try { 117 | const info = await redisClient.info('memory'); 118 | const keyspace = await redisClient.info('keyspace'); 119 | 120 | res.json({ 121 | message: 'Cache statistics retrieved', 122 | memory: info, 123 | keyspace: keyspace 124 | }); 125 | } catch (error) { 126 | res.status(500).json({ 127 | error: 'Failed to get cache statistics', 128 | message: error.message 129 | }); 130 | } 131 | }); 132 | 133 | module.exports = router; 134 | -------------------------------------------------------------------------------- /src/middleware/auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | 3 | /** 4 | * Authentication middleware 5 | * Verifies JWT token and adds user info to request 6 | */ 7 | const authMiddleware = (req, res, next) => { 8 | try { 9 | const authHeader = req.headers.authorization; 10 | 11 | if (!authHeader || !authHeader.startsWith('Bearer ')) { 12 | return res.status(401).json({ 13 | error: 'No token provided' 14 | }); 15 | } 16 | 17 | const token = authHeader.replace('Bearer ', ''); 18 | 19 | // Verify JWT token 20 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 21 | 22 | // Add user info to request 23 | req.user = { 24 | address: decoded.address 25 | }; 26 | 27 | next(); 28 | } catch (error) { 29 | if (error.name === 'JsonWebTokenError') { 30 | return res.status(401).json({ 31 | error: 'Invalid token' 32 | }); 33 | } else if (error.name === 'TokenExpiredError') { 34 | return res.status(401).json({ 35 | error: 'Token expired' 36 | }); 37 | } else { 38 | return res.status(500).json({ 39 | error: 'Authentication error', 40 | message: error.message 41 | }); 42 | } 43 | } 44 | }; 45 | 46 | /** 47 | * Optional authentication middleware 48 | * Verifies JWT token if present but doesn't require it 49 | */ 50 | const optionalAuthMiddleware = (req, res, next) => { 51 | try { 52 | const authHeader = req.headers.authorization; 53 | 54 | if (authHeader && authHeader.startsWith('Bearer ')) { 55 | const token = authHeader.replace('Bearer ', ''); 56 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 57 | req.user = { 58 | address: decoded.address 59 | }; 60 | } 61 | 62 | next(); 63 | } catch (error) { 64 | // Continue without authentication 65 | next(); 66 | } 67 | }; 68 | 69 | /** 70 | * Admin authentication middleware 71 | * Verifies JWT token and checks admin role 72 | */ 73 | const adminAuthMiddleware = (req, res, next) => { 74 | try { 75 | const authHeader = req.headers.authorization; 76 | 77 | if (!authHeader || !authHeader.startsWith('Bearer ')) { 78 | return res.status(401).json({ 79 | error: 'No token provided' 80 | }); 81 | } 82 | 83 | const token = authHeader.replace('Bearer ', ''); 84 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 85 | 86 | // Check if user is admin (in production, check database) 87 | const adminAddresses = process.env.ADMIN_ADDRESSES?.split(',') || []; 88 | if (!adminAddresses.includes(decoded.address.toLowerCase())) { 89 | return res.status(403).json({ 90 | error: 'Admin access required' 91 | }); 92 | } 93 | 94 | req.user = { 95 | address: decoded.address, 96 | role: 'admin' 97 | }; 98 | 99 | next(); 100 | } catch (error) { 101 | if (error.name === 'JsonWebTokenError') { 102 | return res.status(401).json({ 103 | error: 'Invalid token' 104 | }); 105 | } else if (error.name === 'TokenExpiredError') { 106 | return res.status(401).json({ 107 | error: 'Token expired' 108 | }); 109 | } else { 110 | return res.status(500).json({ 111 | error: 'Authentication error', 112 | message: error.message 113 | }); 114 | } 115 | } 116 | }; 117 | 118 | module.exports = { 119 | authMiddleware, 120 | optionalAuthMiddleware, 121 | adminAuthMiddleware 122 | }; 123 | -------------------------------------------------------------------------------- /src/routes/utils.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const rateLimit = require('express-rate-limit'); 3 | 4 | const router = express.Router(); 5 | 6 | // Rate limiting middleware 7 | const limiter = rateLimit({ 8 | windowMs: 15 * 60 * 1000, // 15 minutes 9 | max: 100, // limit each IP to 100 requests per windowMs 10 | message: { 11 | error: 'Too many requests from this IP, please try again later.' 12 | } 13 | }); 14 | 15 | // Apply rate limiting to all routes 16 | router.use(limiter); 17 | 18 | /** 19 | * GET /api/utils/health 20 | * Enhanced health check endpoint 21 | */ 22 | router.get('/health', (req, res) => { 23 | const healthData = { 24 | status: 'healthy', 25 | timestamp: new Date().toISOString(), 26 | uptime: process.uptime(), 27 | memory: process.memoryUsage(), 28 | version: process.env.npm_package_version || '0.1.0', 29 | environment: process.env.NODE_ENV || 'development' 30 | }; 31 | 32 | res.json(healthData); 33 | }); 34 | 35 | /** 36 | * GET /api/utils/metrics 37 | * Get system metrics 38 | */ 39 | router.get('/metrics', (req, res) => { 40 | const metrics = { 41 | timestamp: new Date().toISOString(), 42 | system: { 43 | uptime: process.uptime(), 44 | memory: process.memoryUsage(), 45 | cpu: process.cpuUsage(), 46 | platform: process.platform, 47 | nodeVersion: process.version 48 | }, 49 | requests: { 50 | total: req.app.locals.requestCount || 0, 51 | active: req.app.locals.activeRequests || 0 52 | } 53 | }; 54 | 55 | res.json(metrics); 56 | }); 57 | 58 | /** 59 | * POST /api/utils/validate-address 60 | * Validate Ethereum address 61 | */ 62 | router.post('/validate-address', (req, res) => { 63 | try { 64 | const { address } = req.body; 65 | 66 | if (!address) { 67 | return res.status(400).json({ 68 | error: 'Address is required' 69 | }); 70 | } 71 | 72 | const isValid = /^0x[a-fA-F0-9]{40}$/.test(address); 73 | 74 | res.json({ 75 | address: address, 76 | valid: isValid, 77 | checksum: isValid ? address.toLowerCase() : null 78 | }); 79 | } catch (error) { 80 | res.status(500).json({ 81 | error: 'Address validation failed', 82 | message: error.message 83 | }); 84 | } 85 | }); 86 | 87 | /** 88 | * POST /api/utils/hash-data 89 | * Generate hash for data 90 | */ 91 | router.post('/hash-data', (req, res) => { 92 | try { 93 | const { data, algorithm = 'sha256' } = req.body; 94 | 95 | if (!data) { 96 | return res.status(400).json({ 97 | error: 'Data is required' 98 | }); 99 | } 100 | 101 | const crypto = require('crypto'); 102 | const hash = crypto.createHash(algorithm).update(data).digest('hex'); 103 | 104 | res.json({ 105 | data: data, 106 | algorithm: algorithm, 107 | hash: hash, 108 | timestamp: new Date().toISOString() 109 | }); 110 | } catch (error) { 111 | res.status(500).json({ 112 | error: 'Hash generation failed', 113 | message: error.message 114 | }); 115 | } 116 | }); 117 | 118 | /** 119 | * GET /api/utils/timestamp 120 | * Get current timestamp 121 | */ 122 | router.get('/timestamp', (req, res) => { 123 | const now = new Date(); 124 | 125 | res.json({ 126 | timestamp: now.toISOString(), 127 | unix: Math.floor(now.getTime() / 1000), 128 | milliseconds: now.getTime(), 129 | timezone: Intl.DateTimeFormat().resolvedOptions().timeZone 130 | }); 131 | }); 132 | 133 | module.exports = router; 134 | -------------------------------------------------------------------------------- /src/services/EncryptionService.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const fs = require('fs').promises; 3 | const path = require('path'); 4 | 5 | /** 6 | * Encryption service for handling data encryption/decryption 7 | */ 8 | class EncryptionService { 9 | constructor() { 10 | this.algorithm = 'aes-256-gcm'; 11 | this.keyLength = 32; // 256 bits 12 | this.ivLength = 16; // 128 bits 13 | this.tagLength = 16; // 128 bits 14 | } 15 | 16 | /** 17 | * Generate a random encryption key 18 | * @returns {Buffer} Random encryption key 19 | */ 20 | generateKey() { 21 | return crypto.randomBytes(this.keyLength); 22 | } 23 | 24 | /** 25 | * Generate a random initialization vector 26 | * @returns {Buffer} Random IV 27 | */ 28 | generateIV() { 29 | return crypto.randomBytes(this.ivLength); 30 | } 31 | 32 | /** 33 | * Encrypt data using AES-256-GCM 34 | * @param {string|Buffer} data - Data to encrypt 35 | * @param {Buffer} key - Encryption key 36 | * @returns {Object} Encrypted data with metadata 37 | */ 38 | encrypt(data, key) { 39 | try { 40 | const iv = this.generateIV(); 41 | const cipher = crypto.createCipher(this.algorithm, key); 42 | cipher.setAAD(Buffer.from('zerodata', 'utf8')); 43 | 44 | let encrypted = cipher.update(data, 'utf8', 'hex'); 45 | encrypted += cipher.final('hex'); 46 | 47 | const tag = cipher.getAuthTag(); 48 | 49 | return { 50 | encrypted: encrypted, 51 | iv: iv.toString('hex'), 52 | tag: tag.toString('hex'), 53 | algorithm: this.algorithm 54 | }; 55 | } catch (error) { 56 | throw new Error(`Encryption failed: ${error.message}`); 57 | } 58 | } 59 | 60 | /** 61 | * Decrypt data using AES-256-GCM 62 | * @param {Object} encryptedData - Encrypted data object 63 | * @param {Buffer} key - Decryption key 64 | * @returns {string} Decrypted data 65 | */ 66 | decrypt(encryptedData, key) { 67 | try { 68 | const decipher = crypto.createDecipher( 69 | encryptedData.algorithm, 70 | key 71 | ); 72 | decipher.setAAD(Buffer.from('zerodata', 'utf8')); 73 | decipher.setAuthTag(Buffer.from(encryptedData.tag, 'hex')); 74 | 75 | let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8'); 76 | decrypted += decipher.final('utf8'); 77 | 78 | return decrypted; 79 | } catch (error) { 80 | throw new Error(`Decryption failed: ${error.message}`); 81 | } 82 | } 83 | 84 | /** 85 | * Generate SHA-256 hash of data 86 | * @param {string|Buffer} data - Data to hash 87 | * @returns {string} SHA-256 hash in hex format 88 | */ 89 | generateHash(data) { 90 | return crypto.createHash('sha256').update(data).digest('hex'); 91 | } 92 | 93 | /** 94 | * Generate HMAC for data integrity verification 95 | * @param {string|Buffer} data - Data to sign 96 | * @param {Buffer} key - HMAC key 97 | * @returns {string} HMAC in hex format 98 | */ 99 | generateHMAC(data, key) { 100 | return crypto.createHmac('sha256', key).update(data).digest('hex'); 101 | } 102 | 103 | /** 104 | * Verify HMAC for data integrity 105 | * @param {string|Buffer} data - Data to verify 106 | * @param {Buffer} key - HMAC key 107 | * @param {string} hmac - Expected HMAC 108 | * @returns {boolean} True if HMAC is valid 109 | */ 110 | verifyHMAC(data, key, hmac) { 111 | const expectedHMAC = this.generateHMAC(data, key); 112 | return crypto.timingSafeEqual( 113 | Buffer.from(hmac, 'hex'), 114 | Buffer.from(expectedHMAC, 'hex') 115 | ); 116 | } 117 | 118 | /** 119 | * Generate a secure random string 120 | * @param {number} length - Length of random string 121 | * @returns {string} Random string 122 | */ 123 | generateRandomString(length = 32) { 124 | return crypto.randomBytes(length).toString('hex'); 125 | } 126 | } 127 | 128 | module.exports = EncryptionService; 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZeroData 2 | 3 | A decentralized data-sharing network powered by zero-knowledge proofs. ZeroData allows users to prove the validity of their data without exposing the raw information, enabling secure and private data transactions in a decentralized ecosystem. 4 | 5 | ## Features 6 | 7 | - **Privacy-Preserving Data Sharing**: Use zero-knowledge proofs to verify data without revealing it 8 | - **Decentralized Storage**: Data encrypted and stored on IPFS/Arweave 9 | - **Smart Contract Integration**: Ethereum-based access control and payment settlement 10 | - **DID Identity Management**: Decentralized identity for secure authentication 11 | - **Data Trading Market**: Transparent data transactions with ERC-20 token payments 12 | - **DAO Governance**: Community-driven decision making 13 | 14 | ## Architecture 15 | 16 | ### Core Components 17 | 18 | 1. **Blockchain Layer**: Ethereum L2 (StarkNet/zkSync) for cost efficiency 19 | 2. **Cryptography Layer**: zk-SNARKs/zk-STARKs for zero-knowledge proofs 20 | 3. **Storage Layer**: IPFS/Arweave for encrypted data storage 21 | 4. **Client SDK**: Cross-language SDKs for easy integration 22 | 23 | ### Smart Contracts 24 | 25 | - `DataRegistry`: Manages data metadata and proofs 26 | - `AccessControl`: Handles permission management 27 | - `PaymentSettlement`: Processes data transactions 28 | 29 | ## Getting Started 30 | 31 | ### Prerequisites 32 | 33 | - Node.js >= 16.0.0 34 | - Git 35 | - Ethereum wallet (MetaMask recommended) 36 | 37 | ### Installation 38 | 39 | ```bash 40 | # Clone the repository 41 | git clone https://github.com/jeanettaskafidas/ZeroData.git 42 | cd ZeroData 43 | 44 | # Install dependencies 45 | npm install 46 | 47 | # Set up environment variables 48 | cp .env.example .env 49 | # Edit .env with your configuration 50 | 51 | # Start the development server 52 | npm run dev 53 | ``` 54 | 55 | ### Environment Variables 56 | 57 | Create a `.env` file with the following variables: 58 | 59 | ```env 60 | PORT=3000 61 | NODE_ENV=development 62 | ETHEREUM_RPC_URL=https://goerli.infura.io/v3/YOUR_PROJECT_ID 63 | IPFS_API_URL=http://localhost:5001 64 | DATABASE_URL=mongodb://localhost:27017/zerodata 65 | REDIS_URL=redis://localhost:6379 66 | JWT_SECRET=your-jwt-secret 67 | ``` 68 | 69 | ## Usage 70 | 71 | ### Upload Data 72 | 73 | ```javascript 74 | const { ZeroDataClient } = require('zerodata-sdk'); 75 | 76 | const client = new ZeroDataClient({ 77 | rpcUrl: process.env.ETHEREUM_RPC_URL, 78 | privateKey: process.env.PRIVATE_KEY 79 | }); 80 | 81 | // Upload encrypted data 82 | const result = await client.uploadData({ 83 | data: "sensitive information", 84 | accessPolicy: "public", 85 | price: "1000000000000000000" // 1 token in wei 86 | }); 87 | ``` 88 | 89 | ### Verify Data Access 90 | 91 | ```javascript 92 | // Generate zero-knowledge proof 93 | const proof = await client.generateProof({ 94 | dataHash: result.dataHash, 95 | accessor: "0x...", 96 | conditions: ["age > 18", "verified == true"] 97 | }); 98 | 99 | // Verify proof without revealing data 100 | const isValid = await client.verifyProof(proof); 101 | ``` 102 | 103 | ## Development 104 | 105 | ### Project Structure 106 | 107 | ``` 108 | src/ 109 | ├── contracts/ # Smart contracts 110 | ├── circuits/ # Zero-knowledge proof circuits 111 | ├── services/ # Core business logic 112 | ├── utils/ # Utility functions 113 | ├── middleware/ # Express middleware 114 | ├── routes/ # API routes 115 | └── tests/ # Test files 116 | ``` 117 | 118 | ### Running Tests 119 | 120 | ```bash 121 | npm test 122 | ``` 123 | 124 | ### Building 125 | 126 | ```bash 127 | npm run build 128 | ``` 129 | 130 | ## Contributing 131 | 132 | 1. Fork the repository 133 | 2. Create a feature branch (`git checkout -b feature/amazing-feature`) 134 | 3. Commit your changes (`git commit -m 'feat: add amazing feature'`) 135 | 4. Push to the branch (`git push origin feature/amazing-feature`) 136 | 5. Open a Pull Request 137 | 138 | ## Tokenomics 139 | 140 | The ZeroData ecosystem uses a native ERC-20 token for: 141 | 142 | - **Data Transactions**: Payment for data access 143 | - **Node Incentives**: Rewards for storage and validation nodes 144 | - **Governance**: DAO voting rights 145 | 146 | ### Incentive Structure 147 | 148 | - **Data Providers**: Earn tokens for sharing valuable data 149 | - **Validators**: Receive rewards for proof computation services 150 | - **Storage Nodes**: Get stable income for long-term data storage 151 | 152 | ## Security 153 | 154 | - All smart contracts undergo third-party security audits 155 | - GDPR/CCPA compliant data handling 156 | - Data abuse reporting and arbitration mechanisms 157 | - Regular security updates and patches 158 | 159 | ## Roadmap 160 | 161 | - [x] **Phase 1 (MVP)**: Data upload, encryption, and ZKP verification 162 | - [ ] **Phase 2**: Data trading market and incentive mechanisms 163 | - [ ] **Phase 3**: DID integration and multi-chain interoperability 164 | - [ ] **Phase 4**: DAO governance and developer ecosystem 165 | 166 | ## License 167 | 168 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 169 | 170 | ## Support 171 | 172 | - Documentation: [docs.zerodata.io](https://docs.zerodata.io) 173 | - Discord: [discord.gg/zerodata](https://discord.gg/zerodata) 174 | - Twitter: [@ZeroDataProtocol](https://twitter.com/ZeroDataProtocol) 175 | 176 | ## Acknowledgments 177 | 178 | - Circom for zero-knowledge proof circuits 179 | - IPFS for decentralized storage 180 | - Ethereum Foundation for blockchain infrastructure 181 | -------------------------------------------------------------------------------- /src/services/IPFSService.js: -------------------------------------------------------------------------------- 1 | const { create } = require('ipfs-http-client'); 2 | const fs = require('fs').promises; 3 | const path = require('path'); 4 | 5 | /** 6 | * IPFS service for decentralized storage operations 7 | */ 8 | class IPFSService { 9 | constructor(apiUrl = 'http://localhost:5001') { 10 | this.client = create(apiUrl); 11 | this.pinTimeout = 30000; // 30 seconds 12 | } 13 | 14 | /** 15 | * Upload data to IPFS 16 | * @param {Buffer|string} data - Data to upload 17 | * @param {Object} options - Upload options 18 | * @returns {Promise} IPFS hash and metadata 19 | */ 20 | async uploadData(data, options = {}) { 21 | try { 22 | const result = await this.client.add(data, { 23 | pin: true, 24 | ...options 25 | }); 26 | 27 | // Wait for pinning to complete 28 | await this.waitForPin(result.cid.toString()); 29 | 30 | return { 31 | hash: result.cid.toString(), 32 | size: result.size, 33 | pinned: true, 34 | timestamp: new Date().toISOString() 35 | }; 36 | } catch (error) { 37 | throw new Error(`IPFS upload failed: ${error.message}`); 38 | } 39 | } 40 | 41 | /** 42 | * Download data from IPFS 43 | * @param {string} hash - IPFS hash 44 | * @returns {Promise} Downloaded data 45 | */ 46 | async downloadData(hash) { 47 | try { 48 | const chunks = []; 49 | for await (const chunk of this.client.cat(hash)) { 50 | chunks.push(chunk); 51 | } 52 | return Buffer.concat(chunks); 53 | } catch (error) { 54 | throw new Error(`IPFS download failed: ${error.message}`); 55 | } 56 | } 57 | 58 | /** 59 | * Check if data exists in IPFS 60 | * @param {string} hash - IPFS hash 61 | * @returns {Promise} True if data exists 62 | */ 63 | async dataExists(hash) { 64 | try { 65 | await this.client.stat(hash); 66 | return true; 67 | } catch (error) { 68 | return false; 69 | } 70 | } 71 | 72 | /** 73 | * Get data statistics from IPFS 74 | * @param {string} hash - IPFS hash 75 | * @returns {Promise} Data statistics 76 | */ 77 | async getDataStats(hash) { 78 | try { 79 | const stats = await this.client.stat(hash); 80 | return { 81 | hash: hash, 82 | size: stats.size, 83 | cumulativeSize: stats.cumulativeSize, 84 | blocks: stats.blocks, 85 | type: stats.type 86 | }; 87 | } catch (error) { 88 | throw new Error(`Failed to get IPFS stats: ${error.message}`); 89 | } 90 | } 91 | 92 | /** 93 | * Pin data to local IPFS node 94 | * @param {string} hash - IPFS hash to pin 95 | * @returns {Promise} Pin result 96 | */ 97 | async pinData(hash) { 98 | try { 99 | const result = await this.client.pin.add(hash); 100 | return { 101 | hash: hash, 102 | pinned: true, 103 | timestamp: new Date().toISOString() 104 | }; 105 | } catch (error) { 106 | throw new Error(`Failed to pin data: ${error.message}`); 107 | } 108 | } 109 | 110 | /** 111 | * Unpin data from local IPFS node 112 | * @param {string} hash - IPFS hash to unpin 113 | * @returns {Promise} Unpin result 114 | */ 115 | async unpinData(hash) { 116 | try { 117 | await this.client.pin.rm(hash); 118 | return { 119 | hash: hash, 120 | pinned: false, 121 | timestamp: new Date().toISOString() 122 | }; 123 | } catch (error) { 124 | throw new Error(`Failed to unpin data: ${error.message}`); 125 | } 126 | } 127 | 128 | /** 129 | * List pinned data 130 | * @returns {Promise} List of pinned hashes 131 | */ 132 | async listPinnedData() { 133 | try { 134 | const pins = []; 135 | for await (const pin of this.client.pin.ls()) { 136 | pins.push({ 137 | hash: pin.cid.toString(), 138 | type: pin.type 139 | }); 140 | } 141 | return pins; 142 | } catch (error) { 143 | throw new Error(`Failed to list pinned data: ${error.message}`); 144 | } 145 | } 146 | 147 | /** 148 | * Wait for pinning operation to complete 149 | * @param {string} hash - IPFS hash 150 | * @param {number} timeout - Timeout in milliseconds 151 | * @returns {Promise} 152 | */ 153 | async waitForPin(hash, timeout = this.pinTimeout) { 154 | const startTime = Date.now(); 155 | 156 | while (Date.now() - startTime < timeout) { 157 | try { 158 | const stats = await this.client.stat(hash); 159 | if (stats) { 160 | return; 161 | } 162 | } catch (error) { 163 | // Continue waiting 164 | } 165 | 166 | await new Promise(resolve => setTimeout(resolve, 1000)); 167 | } 168 | 169 | throw new Error(`Pin operation timeout for hash: ${hash}`); 170 | } 171 | 172 | /** 173 | * Upload file to IPFS 174 | * @param {string} filePath - Path to file 175 | * @param {Object} options - Upload options 176 | * @returns {Promise} Upload result 177 | */ 178 | async uploadFile(filePath, options = {}) { 179 | try { 180 | const fileBuffer = await fs.readFile(filePath); 181 | return await this.uploadData(fileBuffer, options); 182 | } catch (error) { 183 | throw new Error(`File upload failed: ${error.message}`); 184 | } 185 | } 186 | 187 | /** 188 | * Download file from IPFS and save to local path 189 | * @param {string} hash - IPFS hash 190 | * @param {string} outputPath - Output file path 191 | * @returns {Promise} Download result 192 | */ 193 | async downloadFile(hash, outputPath) { 194 | try { 195 | const data = await this.downloadData(hash); 196 | await fs.writeFile(outputPath, data); 197 | 198 | return { 199 | hash: hash, 200 | outputPath: outputPath, 201 | size: data.length, 202 | timestamp: new Date().toISOString() 203 | }; 204 | } catch (error) { 205 | throw new Error(`File download failed: ${error.message}`); 206 | } 207 | } 208 | } 209 | 210 | module.exports = IPFSService; 211 | -------------------------------------------------------------------------------- /src/routes/data.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const multer = require('multer'); 3 | const EncryptionService = require('../services/EncryptionService'); 4 | const IPFSService = require('../services/IPFSService'); 5 | const ZKProofService = require('../services/ZKProofService'); 6 | const BlockchainService = require('../services/BlockchainService'); 7 | 8 | const router = express.Router(); 9 | 10 | // Initialize services 11 | const encryptionService = new EncryptionService(); 12 | const ipfsService = new IPFSService(process.env.IPFS_API_URL); 13 | const zkProofService = new ZKProofService(); 14 | const blockchainService = new BlockchainService( 15 | process.env.ETHEREUM_RPC_URL, 16 | process.env.PRIVATE_KEY 17 | ); 18 | 19 | // Configure multer for file uploads 20 | const upload = multer({ 21 | storage: multer.memoryStorage(), 22 | limits: { 23 | fileSize: 10 * 1024 * 1024 // 10MB limit 24 | } 25 | }); 26 | 27 | /** 28 | * POST /api/data/upload 29 | * Upload and encrypt data to IPFS 30 | */ 31 | router.post('/upload', upload.single('data'), async (req, res) => { 32 | try { 33 | const { accessPolicy, price, description } = req.body; 34 | const data = req.file ? req.file.buffer : Buffer.from(req.body.data, 'utf8'); 35 | 36 | if (!data) { 37 | return res.status(400).json({ 38 | error: 'No data provided' 39 | }); 40 | } 41 | 42 | // Generate encryption key 43 | const encryptionKey = encryptionService.generateKey(); 44 | 45 | // Encrypt data 46 | const encryptedData = encryptionService.encrypt(data, encryptionKey); 47 | 48 | // Upload encrypted data to IPFS 49 | const ipfsResult = await ipfsService.uploadData( 50 | Buffer.from(encryptedData.encrypted, 'hex') 51 | ); 52 | 53 | // Generate data hash 54 | const dataHash = encryptionService.generateHash(data); 55 | 56 | // Generate zero-knowledge proof for data integrity 57 | const proof = await zkProofService.createDataVerificationProof( 58 | dataHash, 59 | req.user.address, 60 | { accessPolicy, price } 61 | ); 62 | 63 | // Store metadata (excluding sensitive encryption key) 64 | const metadata = { 65 | dataHash: dataHash, 66 | ipfsHash: ipfsResult.hash, 67 | encryptedMetadata: { 68 | iv: encryptedData.iv, 69 | tag: encryptedData.tag, 70 | algorithm: encryptedData.algorithm 71 | }, 72 | accessPolicy: accessPolicy || 'private', 73 | price: price || '0', 74 | description: description || '', 75 | owner: req.user.address, 76 | proof: proof, 77 | uploadedAt: new Date().toISOString() 78 | }; 79 | 80 | // TODO: Store encryption key securely (e.g., in encrypted database) 81 | // For now, we'll return it to the client 82 | const response = { 83 | ...metadata, 84 | encryptionKey: encryptionKey.toString('hex') // Remove in production 85 | }; 86 | 87 | res.status(201).json(response); 88 | } catch (error) { 89 | res.status(500).json({ 90 | error: 'Data upload failed', 91 | message: error.message 92 | }); 93 | } 94 | }); 95 | 96 | /** 97 | * GET /api/data/:dataHash 98 | * Get data metadata 99 | */ 100 | router.get('/:dataHash', async (req, res) => { 101 | try { 102 | const { dataHash } = req.params; 103 | 104 | // TODO: Retrieve metadata from database 105 | // For now, return mock data 106 | const metadata = { 107 | dataHash: dataHash, 108 | accessPolicy: 'private', 109 | price: '1000000000000000000', 110 | description: 'Sample data description', 111 | owner: '0x1234567890123456789012345678901234567890', 112 | uploadedAt: new Date().toISOString() 113 | }; 114 | 115 | res.json(metadata); 116 | } catch (error) { 117 | res.status(500).json({ 118 | error: 'Failed to retrieve data metadata', 119 | message: error.message 120 | }); 121 | } 122 | }); 123 | 124 | /** 125 | * POST /api/data/:dataHash/download 126 | * Request data download with access control 127 | */ 128 | router.post('/:dataHash/download', async (req, res) => { 129 | try { 130 | const { dataHash } = req.params; 131 | const { requesterAddress, permissions } = req.body; 132 | 133 | // Generate access control proof 134 | const accessProof = await zkProofService.createAccessControlProof( 135 | requesterAddress, 136 | dataHash, 137 | permissions 138 | ); 139 | 140 | // TODO: Verify access permissions using smart contract 141 | // TODO: Process payment if required 142 | // TODO: Retrieve and decrypt data 143 | 144 | res.json({ 145 | dataHash: dataHash, 146 | accessProof: accessProof, 147 | status: 'access_granted', 148 | timestamp: new Date().toISOString() 149 | }); 150 | } catch (error) { 151 | res.status(500).json({ 152 | error: 'Data download request failed', 153 | message: error.message 154 | }); 155 | } 156 | }); 157 | 158 | /** 159 | * POST /api/data/:dataHash/verify 160 | * Verify data integrity using zero-knowledge proof 161 | */ 162 | router.post('/:dataHash/verify', async (req, res) => { 163 | try { 164 | const { dataHash } = req.params; 165 | const { proof } = req.body; 166 | 167 | // Verify the proof 168 | const isValid = await zkProofService.verifyDataIntegrity(proof, dataHash); 169 | 170 | res.json({ 171 | dataHash: dataHash, 172 | verified: isValid, 173 | timestamp: new Date().toISOString() 174 | }); 175 | } catch (error) { 176 | res.status(500).json({ 177 | error: 'Proof verification failed', 178 | message: error.message 179 | }); 180 | } 181 | }); 182 | 183 | /** 184 | * GET /api/data/user/:address 185 | * Get all data owned by a user 186 | */ 187 | router.get('/user/:address', async (req, res) => { 188 | try { 189 | const { address } = req.params; 190 | 191 | // TODO: Retrieve user's data from database 192 | const userData = [ 193 | { 194 | dataHash: '0x1234567890abcdef1234567890abcdef12345678', 195 | description: 'User data 1', 196 | accessPolicy: 'public', 197 | price: '1000000000000000000', 198 | uploadedAt: new Date().toISOString() 199 | }, 200 | { 201 | dataHash: '0xabcdef1234567890abcdef1234567890abcdef12', 202 | description: 'User data 2', 203 | accessPolicy: 'private', 204 | price: '2000000000000000000', 205 | uploadedAt: new Date().toISOString() 206 | } 207 | ]; 208 | 209 | res.json({ 210 | address: address, 211 | data: userData, 212 | count: userData.length 213 | }); 214 | } catch (error) { 215 | res.status(500).json({ 216 | error: 'Failed to retrieve user data', 217 | message: error.message 218 | }); 219 | } 220 | }); 221 | 222 | /** 223 | * DELETE /api/data/:dataHash 224 | * Delete data (only by owner) 225 | */ 226 | router.delete('/:dataHash', async (req, res) => { 227 | try { 228 | const { dataHash } = req.params; 229 | 230 | // TODO: Verify ownership 231 | // TODO: Remove from IPFS 232 | // TODO: Update smart contract state 233 | 234 | res.json({ 235 | dataHash: dataHash, 236 | deleted: true, 237 | timestamp: new Date().toISOString() 238 | }); 239 | } catch (error) { 240 | res.status(500).json({ 241 | error: 'Data deletion failed', 242 | message: error.message 243 | }); 244 | } 245 | }); 246 | 247 | module.exports = router; 248 | -------------------------------------------------------------------------------- /src/services/ZKProofService.js: -------------------------------------------------------------------------------- 1 | const snarkjs = require('snarkjs'); 2 | const fs = require('fs').promises; 3 | const path = require('path'); 4 | 5 | /** 6 | * Zero Knowledge Proof service for data verification 7 | */ 8 | class ZKProofService { 9 | constructor() { 10 | this.circuitsPath = path.join(__dirname, '../circuits'); 11 | this.proofsPath = path.join(__dirname, '../proofs'); 12 | this.witnessesPath = path.join(__dirname, '../witnesses'); 13 | } 14 | 15 | /** 16 | * Generate zero-knowledge proof for data verification 17 | * @param {Object} inputs - Circuit inputs 18 | * @param {string} circuitName - Name of the circuit 19 | * @returns {Promise} Generated proof and public signals 20 | */ 21 | async generateProof(inputs, circuitName = 'dataVerification') { 22 | try { 23 | const circuitPath = path.join(this.circuitsPath, `${circuitName}.wasm`); 24 | const provingKeyPath = path.join(this.circuitsPath, `${circuitName}_proving_key.zkey`); 25 | 26 | // Check if circuit files exist 27 | await this.checkCircuitFiles(circuitPath, provingKeyPath); 28 | 29 | // Generate witness 30 | const witness = await this.generateWitness(inputs, circuitPath); 31 | 32 | // Generate proof 33 | const { proof, publicSignals } = await snarkjs.groth16.prove( 34 | provingKeyPath, 35 | witness 36 | ); 37 | 38 | // Save proof for verification 39 | const proofData = { 40 | proof: proof, 41 | publicSignals: publicSignals, 42 | circuitName: circuitName, 43 | timestamp: new Date().toISOString() 44 | }; 45 | 46 | await this.saveProof(proofData, circuitName); 47 | 48 | return { 49 | proof: proof, 50 | publicSignals: publicSignals, 51 | circuitName: circuitName, 52 | timestamp: new Date().toISOString() 53 | }; 54 | } catch (error) { 55 | throw new Error(`Proof generation failed: ${error.message}`); 56 | } 57 | } 58 | 59 | /** 60 | * Verify zero-knowledge proof 61 | * @param {Object} proofData - Proof data to verify 62 | * @param {string} circuitName - Name of the circuit 63 | * @returns {Promise} True if proof is valid 64 | */ 65 | async verifyProof(proofData, circuitName = 'dataVerification') { 66 | try { 67 | const verificationKeyPath = path.join(this.circuitsPath, `${circuitName}_verification_key.json`); 68 | 69 | // Check if verification key exists 70 | await fs.access(verificationKeyPath); 71 | 72 | // Verify proof 73 | const isValid = await snarkjs.groth16.verify( 74 | JSON.parse(await fs.readFile(verificationKeyPath, 'utf8')), 75 | proofData.publicSignals, 76 | proofData.proof 77 | ); 78 | 79 | return isValid; 80 | } catch (error) { 81 | throw new Error(`Proof verification failed: ${error.message}`); 82 | } 83 | } 84 | 85 | /** 86 | * Generate witness for circuit inputs 87 | * @param {Object} inputs - Circuit inputs 88 | * @param {string} circuitPath - Path to circuit WASM file 89 | * @returns {Promise} Generated witness 90 | */ 91 | async generateWitness(inputs, circuitPath) { 92 | try { 93 | const witness = await snarkjs.wtns.calculate( 94 | inputs, 95 | circuitPath 96 | ); 97 | 98 | return witness; 99 | } catch (error) { 100 | throw new Error(`Witness generation failed: ${error.message}`); 101 | } 102 | } 103 | 104 | /** 105 | * Check if required circuit files exist 106 | * @param {string} circuitPath - Path to circuit WASM file 107 | * @param {string} provingKeyPath - Path to proving key 108 | * @throws {Error} If files don't exist 109 | */ 110 | async checkCircuitFiles(circuitPath, provingKeyPath) { 111 | try { 112 | await fs.access(circuitPath); 113 | await fs.access(provingKeyPath); 114 | } catch (error) { 115 | throw new Error(`Circuit files not found. Please compile circuits first.`); 116 | } 117 | } 118 | 119 | /** 120 | * Save proof to file system 121 | * @param {Object} proofData - Proof data to save 122 | * @param {string} circuitName - Circuit name 123 | * @returns {Promise} Path to saved proof file 124 | */ 125 | async saveProof(proofData, circuitName) { 126 | try { 127 | const proofId = this.generateProofId(); 128 | const proofPath = path.join(this.proofsPath, `${circuitName}_${proofId}.json`); 129 | 130 | await fs.writeFile(proofPath, JSON.stringify(proofData, null, 2)); 131 | 132 | return proofPath; 133 | } catch (error) { 134 | throw new Error(`Failed to save proof: ${error.message}`); 135 | } 136 | } 137 | 138 | /** 139 | * Load proof from file system 140 | * @param {string} proofPath - Path to proof file 141 | * @returns {Promise} Loaded proof data 142 | */ 143 | async loadProof(proofPath) { 144 | try { 145 | const proofData = JSON.parse(await fs.readFile(proofPath, 'utf8')); 146 | return proofData; 147 | } catch (error) { 148 | throw new Error(`Failed to load proof: ${error.message}`); 149 | } 150 | } 151 | 152 | /** 153 | * Generate unique proof ID 154 | * @returns {string} Unique proof ID 155 | */ 156 | generateProofId() { 157 | return `proof_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; 158 | } 159 | 160 | /** 161 | * Create data verification proof 162 | * @param {string} dataHash - Hash of the data 163 | * @param {string} ownerAddress - Owner's Ethereum address 164 | * @param {Object} conditions - Verification conditions 165 | * @returns {Promise} Data verification proof 166 | */ 167 | async createDataVerificationProof(dataHash, ownerAddress, conditions = {}) { 168 | const inputs = { 169 | dataHash: dataHash, 170 | ownerAddress: ownerAddress, 171 | timestamp: Math.floor(Date.now() / 1000), 172 | ...conditions 173 | }; 174 | 175 | return await this.generateProof(inputs, 'dataVerification'); 176 | } 177 | 178 | /** 179 | * Create access control proof 180 | * @param {string} requesterAddress - Requester's address 181 | * @param {string} dataHash - Hash of requested data 182 | * @param {Object} permissions - Required permissions 183 | * @returns {Promise} Access control proof 184 | */ 185 | async createAccessControlProof(requesterAddress, dataHash, permissions = {}) { 186 | const inputs = { 187 | requesterAddress: requesterAddress, 188 | dataHash: dataHash, 189 | permissions: JSON.stringify(permissions), 190 | timestamp: Math.floor(Date.now() / 1000) 191 | }; 192 | 193 | return await this.generateProof(inputs, 'accessControl'); 194 | } 195 | 196 | /** 197 | * Verify data integrity proof 198 | * @param {Object} proofData - Proof data 199 | * @param {string} expectedDataHash - Expected data hash 200 | * @returns {Promise} True if data integrity is verified 201 | */ 202 | async verifyDataIntegrity(proofData, expectedDataHash) { 203 | try { 204 | const isValid = await this.verifyProof(proofData); 205 | 206 | if (!isValid) { 207 | return false; 208 | } 209 | 210 | // Check if public signals match expected data hash 211 | const publicSignals = proofData.publicSignals; 212 | return publicSignals[0] === expectedDataHash; 213 | } catch (error) { 214 | throw new Error(`Data integrity verification failed: ${error.message}`); 215 | } 216 | } 217 | } 218 | 219 | module.exports = ZKProofService; 220 | -------------------------------------------------------------------------------- /src/routes/auth.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const jwt = require('jsonwebtoken'); 3 | const bcrypt = require('bcryptjs'); 4 | const crypto = require('crypto'); 5 | 6 | const router = express.Router(); 7 | 8 | // Mock user database (replace with real database in production) 9 | const users = new Map(); 10 | const sessions = new Map(); 11 | 12 | /** 13 | * POST /api/auth/register 14 | * Register a new user 15 | */ 16 | router.post('/register', async (req, res) => { 17 | try { 18 | const { address, signature, message } = req.body; 19 | 20 | if (!address || !signature || !message) { 21 | return res.status(400).json({ 22 | error: 'Missing required fields: address, signature, message' 23 | }); 24 | } 25 | 26 | // Verify Ethereum signature 27 | const isValidSignature = await verifyEthereumSignature(address, signature, message); 28 | 29 | if (!isValidSignature) { 30 | return res.status(400).json({ 31 | error: 'Invalid signature' 32 | }); 33 | } 34 | 35 | // Check if user already exists 36 | if (users.has(address.toLowerCase())) { 37 | return res.status(409).json({ 38 | error: 'User already exists' 39 | }); 40 | } 41 | 42 | // Create new user 43 | const user = { 44 | address: address.toLowerCase(), 45 | registeredAt: new Date().toISOString(), 46 | lastLogin: null, 47 | dataCount: 0, 48 | totalEarnings: '0' 49 | }; 50 | 51 | users.set(address.toLowerCase(), user); 52 | 53 | // Generate JWT token 54 | const token = jwt.sign( 55 | { address: address.toLowerCase() }, 56 | process.env.JWT_SECRET, 57 | { expiresIn: '24h' } 58 | ); 59 | 60 | res.status(201).json({ 61 | message: 'User registered successfully', 62 | token: token, 63 | user: { 64 | address: user.address, 65 | registeredAt: user.registeredAt 66 | } 67 | }); 68 | } catch (error) { 69 | res.status(500).json({ 70 | error: 'Registration failed', 71 | message: error.message 72 | }); 73 | } 74 | }); 75 | 76 | /** 77 | * POST /api/auth/login 78 | * Login with Ethereum signature 79 | */ 80 | router.post('/login', async (req, res) => { 81 | try { 82 | const { address, signature, message } = req.body; 83 | 84 | if (!address || !signature || !message) { 85 | return res.status(400).json({ 86 | error: 'Missing required fields: address, signature, message' 87 | }); 88 | } 89 | 90 | // Verify Ethereum signature 91 | const isValidSignature = await verifyEthereumSignature(address, signature, message); 92 | 93 | if (!isValidSignature) { 94 | return res.status(401).json({ 95 | error: 'Invalid signature' 96 | }); 97 | } 98 | 99 | // Check if user exists 100 | const user = users.get(address.toLowerCase()); 101 | if (!user) { 102 | return res.status(404).json({ 103 | error: 'User not found' 104 | }); 105 | } 106 | 107 | // Update last login 108 | user.lastLogin = new Date().toISOString(); 109 | users.set(address.toLowerCase(), user); 110 | 111 | // Generate JWT token 112 | const token = jwt.sign( 113 | { address: address.toLowerCase() }, 114 | process.env.JWT_SECRET, 115 | { expiresIn: '24h' } 116 | ); 117 | 118 | res.json({ 119 | message: 'Login successful', 120 | token: token, 121 | user: { 122 | address: user.address, 123 | registeredAt: user.registeredAt, 124 | lastLogin: user.lastLogin, 125 | dataCount: user.dataCount, 126 | totalEarnings: user.totalEarnings 127 | } 128 | }); 129 | } catch (error) { 130 | res.status(500).json({ 131 | error: 'Login failed', 132 | message: error.message 133 | }); 134 | } 135 | }); 136 | 137 | /** 138 | * POST /api/auth/logout 139 | * Logout user 140 | */ 141 | router.post('/logout', async (req, res) => { 142 | try { 143 | const token = req.headers.authorization?.replace('Bearer ', ''); 144 | 145 | if (token) { 146 | // Add token to blacklist (in production, use Redis) 147 | sessions.set(token, { 148 | blacklisted: true, 149 | blacklistedAt: new Date().toISOString() 150 | }); 151 | } 152 | 153 | res.json({ 154 | message: 'Logout successful' 155 | }); 156 | } catch (error) { 157 | res.status(500).json({ 158 | error: 'Logout failed', 159 | message: error.message 160 | }); 161 | } 162 | }); 163 | 164 | /** 165 | * GET /api/auth/profile 166 | * Get user profile 167 | */ 168 | router.get('/profile', async (req, res) => { 169 | try { 170 | const user = users.get(req.user.address.toLowerCase()); 171 | 172 | if (!user) { 173 | return res.status(404).json({ 174 | error: 'User not found' 175 | }); 176 | } 177 | 178 | res.json({ 179 | address: user.address, 180 | registeredAt: user.registeredAt, 181 | lastLogin: user.lastLogin, 182 | dataCount: user.dataCount, 183 | totalEarnings: user.totalEarnings 184 | }); 185 | } catch (error) { 186 | res.status(500).json({ 187 | error: 'Failed to get profile', 188 | message: error.message 189 | }); 190 | } 191 | }); 192 | 193 | /** 194 | * POST /api/auth/refresh 195 | * Refresh JWT token 196 | */ 197 | router.post('/refresh', async (req, res) => { 198 | try { 199 | const { address } = req.body; 200 | 201 | if (!address) { 202 | return res.status(400).json({ 203 | error: 'Address is required' 204 | }); 205 | } 206 | 207 | const user = users.get(address.toLowerCase()); 208 | if (!user) { 209 | return res.status(404).json({ 210 | error: 'User not found' 211 | }); 212 | } 213 | 214 | // Generate new JWT token 215 | const token = jwt.sign( 216 | { address: address.toLowerCase() }, 217 | process.env.JWT_SECRET, 218 | { expiresIn: '24h' } 219 | ); 220 | 221 | res.json({ 222 | message: 'Token refreshed successfully', 223 | token: token 224 | }); 225 | } catch (error) { 226 | res.status(500).json({ 227 | error: 'Token refresh failed', 228 | message: error.message 229 | }); 230 | } 231 | }); 232 | 233 | /** 234 | * Verify Ethereum signature 235 | * @param {string} address - Ethereum address 236 | * @param {string} signature - Signature 237 | * @param {string} message - Original message 238 | * @returns {Promise} True if signature is valid 239 | */ 240 | async function verifyEthereumSignature(address, signature, message) { 241 | try { 242 | const { ethers } = require('ethers'); 243 | 244 | // Recover address from signature 245 | const recoveredAddress = ethers.verifyMessage(message, signature); 246 | 247 | // Compare addresses (case-insensitive) 248 | return recoveredAddress.toLowerCase() === address.toLowerCase(); 249 | } catch (error) { 250 | return false; 251 | } 252 | } 253 | 254 | /** 255 | * Generate authentication message 256 | * @param {string} address - Ethereum address 257 | * @returns {string} Authentication message 258 | */ 259 | function generateAuthMessage(address) { 260 | const timestamp = Math.floor(Date.now() / 1000); 261 | return `ZeroData Authentication\nAddress: ${address}\nTimestamp: ${timestamp}`; 262 | } 263 | 264 | /** 265 | * GET /api/auth/message/:address 266 | * Get authentication message for signing 267 | */ 268 | router.get('/message/:address', (req, res) => { 269 | try { 270 | const { address } = req.params; 271 | 272 | if (!address || !address.match(/^0x[a-fA-F0-9]{40}$/)) { 273 | return res.status(400).json({ 274 | error: 'Invalid Ethereum address' 275 | }); 276 | } 277 | 278 | const message = generateAuthMessage(address); 279 | 280 | res.json({ 281 | address: address, 282 | message: message, 283 | timestamp: Math.floor(Date.now() / 1000) 284 | }); 285 | } catch (error) { 286 | res.status(500).json({ 287 | error: 'Failed to generate auth message', 288 | message: error.message 289 | }); 290 | } 291 | }); 292 | 293 | module.exports = router; 294 | -------------------------------------------------------------------------------- /src/models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const bcrypt = require('bcryptjs'); 3 | const jwt = require('jsonwebtoken'); 4 | 5 | /** 6 | * User schema for MongoDB 7 | */ 8 | const userSchema = new mongoose.Schema({ 9 | address: { 10 | type: String, 11 | required: true, 12 | unique: true, 13 | lowercase: true, 14 | match: [/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address'] 15 | }, 16 | username: { 17 | type: String, 18 | unique: true, 19 | sparse: true, 20 | minlength: 3, 21 | maxlength: 30 22 | }, 23 | email: { 24 | type: String, 25 | unique: true, 26 | sparse: true, 27 | lowercase: true, 28 | match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, 'Invalid email format'] 29 | }, 30 | profile: { 31 | firstName: String, 32 | lastName: String, 33 | bio: String, 34 | avatar: String, 35 | website: String, 36 | location: String 37 | }, 38 | preferences: { 39 | notifications: { 40 | email: { type: Boolean, default: true }, 41 | push: { type: Boolean, default: true }, 42 | marketing: { type: Boolean, default: false } 43 | }, 44 | privacy: { 45 | profileVisibility: { type: String, enum: ['public', 'private', 'friends'], default: 'public' }, 46 | dataSharing: { type: Boolean, default: true } 47 | }, 48 | language: { type: String, default: 'en' }, 49 | timezone: { type: String, default: 'UTC' } 50 | }, 51 | stats: { 52 | dataCount: { type: Number, default: 0 }, 53 | totalEarnings: { type: String, default: '0' }, 54 | totalSpent: { type: String, default: '0' }, 55 | reputationScore: { type: Number, default: 0 }, 56 | lastActive: { type: Date, default: Date.now } 57 | }, 58 | verification: { 59 | emailVerified: { type: Boolean, default: false }, 60 | phoneVerified: { type: Boolean, default: false }, 61 | identityVerified: { type: Boolean, default: false }, 62 | kycStatus: { type: String, enum: ['none', 'pending', 'approved', 'rejected'], default: 'none' } 63 | }, 64 | security: { 65 | twoFactorEnabled: { type: Boolean, default: false }, 66 | twoFactorSecret: String, 67 | backupCodes: [String], 68 | lastPasswordChange: Date, 69 | loginAttempts: { type: Number, default: 0 }, 70 | lockUntil: Date 71 | }, 72 | tokens: [{ 73 | token: String, 74 | type: { type: String, enum: ['access', 'refresh', 'reset'], required: true }, 75 | expiresAt: Date, 76 | createdAt: { type: Date, default: Date.now } 77 | }], 78 | roles: [{ 79 | type: String, 80 | enum: ['user', 'moderator', 'admin', 'superadmin'] 81 | }], 82 | status: { 83 | type: String, 84 | enum: ['active', 'inactive', 'suspended', 'banned'], 85 | default: 'active' 86 | } 87 | }, { 88 | timestamps: true, 89 | toJSON: { virtuals: true }, 90 | toObject: { virtuals: true } 91 | }); 92 | 93 | // Indexes 94 | userSchema.index({ address: 1 }); 95 | userSchema.index({ email: 1 }); 96 | userSchema.index({ username: 1 }); 97 | userSchema.index({ 'stats.reputationScore': -1 }); 98 | userSchema.index({ createdAt: -1 }); 99 | 100 | // Virtual fields 101 | userSchema.virtual('fullName').get(function() { 102 | if (this.profile.firstName && this.profile.lastName) { 103 | return `${this.profile.firstName} ${this.profile.lastName}`; 104 | } 105 | return this.username || this.address.slice(0, 10) + '...'; 106 | }); 107 | 108 | userSchema.virtual('isLocked').get(function() { 109 | return !!(this.security.lockUntil && this.security.lockUntil > Date.now()); 110 | }); 111 | 112 | // Pre-save middleware 113 | userSchema.pre('save', async function(next) { 114 | if (!this.isModified('security.twoFactorSecret') || !this.security.twoFactorSecret) { 115 | return next(); 116 | } 117 | 118 | try { 119 | this.security.twoFactorSecret = await bcrypt.hash(this.security.twoFactorSecret, 12); 120 | next(); 121 | } catch (error) { 122 | next(error); 123 | } 124 | }); 125 | 126 | // Instance methods 127 | userSchema.methods.generateAuthToken = function() { 128 | const token = jwt.sign( 129 | { 130 | userId: this._id, 131 | address: this.address, 132 | roles: this.roles 133 | }, 134 | process.env.JWT_SECRET, 135 | { expiresIn: '24h' } 136 | ); 137 | 138 | this.tokens.push({ 139 | token, 140 | type: 'access', 141 | expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) 142 | }); 143 | 144 | return token; 145 | }; 146 | 147 | userSchema.methods.generateRefreshToken = function() { 148 | const token = jwt.sign( 149 | { userId: this._id, type: 'refresh' }, 150 | process.env.JWT_SECRET, 151 | { expiresIn: '7d' } 152 | ); 153 | 154 | this.tokens.push({ 155 | token, 156 | type: 'refresh', 157 | expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) 158 | }); 159 | 160 | return token; 161 | }; 162 | 163 | userSchema.methods.removeToken = function(token) { 164 | this.tokens = this.tokens.filter(t => t.token !== token); 165 | }; 166 | 167 | userSchema.methods.removeAllTokens = function() { 168 | this.tokens = []; 169 | }; 170 | 171 | userSchema.methods.incrementLoginAttempts = function() { 172 | if (this.security.lockUntil && this.security.lockUntil < Date.now()) { 173 | return this.updateOne({ 174 | $unset: { 'security.loginAttempts': 1, 'security.lockUntil': 1 } 175 | }); 176 | } 177 | 178 | const updates = { $inc: { 'security.loginAttempts': 1 } }; 179 | 180 | if (this.security.loginAttempts + 1 >= 5 && !this.isLocked) { 181 | updates.$set = { 'security.lockUntil': Date.now() + 2 * 60 * 60 * 1000 }; // 2 hours 182 | } 183 | 184 | return this.updateOne(updates); 185 | }; 186 | 187 | userSchema.methods.resetLoginAttempts = function() { 188 | return this.updateOne({ 189 | $unset: { 'security.loginAttempts': 1, 'security.lockUntil': 1 } 190 | }); 191 | }; 192 | 193 | userSchema.methods.updateLastActive = function() { 194 | this.stats.lastActive = new Date(); 195 | return this.save(); 196 | }; 197 | 198 | userSchema.methods.incrementDataCount = function() { 199 | this.stats.dataCount += 1; 200 | return this.save(); 201 | }; 202 | 203 | userSchema.methods.addEarnings = function(amount) { 204 | const currentEarnings = BigInt(this.stats.totalEarnings); 205 | const newAmount = BigInt(amount); 206 | this.stats.totalEarnings = (currentEarnings + newAmount).toString(); 207 | return this.save(); 208 | }; 209 | 210 | userSchema.methods.addSpending = function(amount) { 211 | const currentSpent = BigInt(this.stats.totalSpent); 212 | const newAmount = BigInt(amount); 213 | this.stats.totalSpent = (currentSpent + newAmount).toString(); 214 | return this.save(); 215 | }; 216 | 217 | userSchema.methods.updateReputation = function(score) { 218 | this.stats.reputationScore = Math.max(0, Math.min(100, score)); 219 | return this.save(); 220 | }; 221 | 222 | // Static methods 223 | userSchema.statics.findByAddress = function(address) { 224 | return this.findOne({ address: address.toLowerCase() }); 225 | }; 226 | 227 | userSchema.statics.findByEmail = function(email) { 228 | return this.findOne({ email: email.toLowerCase() }); 229 | }; 230 | 231 | userSchema.statics.findByUsername = function(username) { 232 | return this.findOne({ username: username.toLowerCase() }); 233 | }; 234 | 235 | userSchema.statics.findActiveUsers = function() { 236 | return this.find({ status: 'active' }); 237 | }; 238 | 239 | userSchema.statics.findTopUsers = function(limit = 10) { 240 | return this.find({ status: 'active' }) 241 | .sort({ 'stats.reputationScore': -1 }) 242 | .limit(limit); 243 | }; 244 | 245 | userSchema.statics.getUserStats = function() { 246 | return this.aggregate([ 247 | { 248 | $group: { 249 | _id: null, 250 | totalUsers: { $sum: 1 }, 251 | activeUsers: { 252 | $sum: { $cond: [{ $eq: ['$status', 'active'] }, 1, 0] } 253 | }, 254 | totalDataCount: { $sum: '$stats.dataCount' }, 255 | totalEarnings: { $sum: { $toDouble: '$stats.totalEarnings' } }, 256 | averageReputation: { $avg: '$stats.reputationScore' } 257 | } 258 | } 259 | ]); 260 | }; 261 | 262 | // Clean up expired tokens 263 | userSchema.statics.cleanupExpiredTokens = function() { 264 | return this.updateMany( 265 | {}, 266 | { 267 | $pull: { 268 | tokens: { 269 | expiresAt: { $lt: new Date() } 270 | } 271 | } 272 | } 273 | ); 274 | }; 275 | 276 | const User = mongoose.model('User', userSchema); 277 | 278 | module.exports = User; 279 | -------------------------------------------------------------------------------- /src/models/Transaction.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | /** 4 | * Transaction schema for tracking all blockchain transactions 5 | */ 6 | const transactionSchema = new mongoose.Schema({ 7 | hash: { 8 | type: String, 9 | required: true, 10 | unique: true, 11 | match: [/^0x[a-fA-F0-9]{64}$/, 'Invalid transaction hash format'] 12 | }, 13 | network: { 14 | type: String, 15 | required: true, 16 | enum: ['ethereum', 'polygon', 'bsc', 'arbitrum', 'optimism', 'avalanche'] 17 | }, 18 | type: { 19 | type: String, 20 | required: true, 21 | enum: ['data_upload', 'data_purchase', 'data_access', 'staking', 'unstaking', 'reward_claim', 'governance_vote', 'proposal_create', 'proposal_execute', 'token_transfer'] 22 | }, 23 | from: { 24 | type: String, 25 | required: true, 26 | lowercase: true 27 | }, 28 | to: { 29 | type: String, 30 | required: true, 31 | lowercase: true 32 | }, 33 | value: { 34 | type: String, 35 | required: true, 36 | default: '0' 37 | }, 38 | gasUsed: { 39 | type: String, 40 | required: true 41 | }, 42 | gasPrice: { 43 | type: String, 44 | required: true 45 | }, 46 | blockNumber: { 47 | type: Number, 48 | required: true 49 | }, 50 | blockHash: { 51 | type: String, 52 | required: true 53 | }, 54 | status: { 55 | type: String, 56 | enum: ['pending', 'confirmed', 'failed'], 57 | default: 'pending' 58 | }, 59 | confirmations: { 60 | type: Number, 61 | default: 0 62 | }, 63 | data: { 64 | dataHash: String, 65 | contractAddress: String, 66 | methodName: String, 67 | parameters: mongoose.Schema.Types.Mixed, 68 | events: [{ 69 | name: String, 70 | data: mongoose.Schema.Types.Mixed, 71 | blockNumber: Number, 72 | transactionIndex: Number, 73 | logIndex: Number 74 | }] 75 | }, 76 | metadata: { 77 | description: String, 78 | category: String, 79 | tags: [String], 80 | priority: { type: String, enum: ['low', 'medium', 'high'], default: 'medium' } 81 | }, 82 | fees: { 83 | gasFee: String, 84 | networkFee: String, 85 | totalFee: String, 86 | feeCurrency: { type: String, default: 'ETH' } 87 | }, 88 | timestamps: { 89 | submitted: { type: Date, default: Date.now }, 90 | confirmed: Date, 91 | failed: Date 92 | }, 93 | retry: { 94 | attempts: { type: Number, default: 0 }, 95 | maxAttempts: { type: Number, default: 3 }, 96 | lastRetry: Date, 97 | nextRetry: Date 98 | } 99 | }, { 100 | timestamps: true, 101 | toJSON: { virtuals: true }, 102 | toObject: { virtuals: true } 103 | }); 104 | 105 | // Indexes 106 | transactionSchema.index({ hash: 1 }); 107 | transactionSchema.index({ network: 1 }); 108 | transactionSchema.index({ type: 1 }); 109 | transactionSchema.index({ from: 1 }); 110 | transactionSchema.index({ to: 1 }); 111 | transactionSchema.index({ blockNumber: -1 }); 112 | transactionSchema.index({ status: 1 }); 113 | transactionSchema.index({ 'timestamps.submitted': -1 }); 114 | transactionSchema.index({ 'data.dataHash': 1 }); 115 | 116 | // Virtual fields 117 | transactionSchema.virtual('isConfirmed').get(function() { 118 | return this.status === 'confirmed'; 119 | }); 120 | 121 | transactionSchema.virtual('isPending').get(function() { 122 | return this.status === 'pending'; 123 | }); 124 | 125 | transactionSchema.virtual('isFailed').get(function() { 126 | return this.status === 'failed'; 127 | }); 128 | 129 | transactionSchema.virtual('age').get(function() { 130 | return Date.now() - this.timestamps.submitted.getTime(); 131 | }); 132 | 133 | // Instance methods 134 | transactionSchema.methods.markAsConfirmed = function(blockNumber, blockHash) { 135 | this.status = 'confirmed'; 136 | this.blockNumber = blockNumber; 137 | this.blockHash = blockHash; 138 | this.timestamps.confirmed = new Date(); 139 | return this.save(); 140 | }; 141 | 142 | transactionSchema.methods.markAsFailed = function() { 143 | this.status = 'failed'; 144 | this.timestamps.failed = new Date(); 145 | return this.save(); 146 | }; 147 | 148 | transactionSchema.methods.incrementRetry = function() { 149 | this.retry.attempts += 1; 150 | this.retry.lastRetry = new Date(); 151 | this.retry.nextRetry = new Date(Date.now() + Math.pow(2, this.retry.attempts) * 60000); // Exponential backoff 152 | return this.save(); 153 | }; 154 | 155 | transactionSchema.methods.canRetry = function() { 156 | return this.retry.attempts < this.retry.maxAttempts && this.status === 'failed'; 157 | }; 158 | 159 | transactionSchema.methods.addEvent = function(eventData) { 160 | this.data.events.push(eventData); 161 | return this.save(); 162 | }; 163 | 164 | // Static methods 165 | transactionSchema.statics.findByHash = function(hash) { 166 | return this.findOne({ hash: hash }); 167 | }; 168 | 169 | transactionSchema.statics.findByNetwork = function(network) { 170 | return this.find({ network: network }).sort({ blockNumber: -1 }); 171 | }; 172 | 173 | transactionSchema.statics.findByType = function(type) { 174 | return this.find({ type: type }).sort({ blockNumber: -1 }); 175 | }; 176 | 177 | transactionSchema.statics.findByAddress = function(address) { 178 | return this.find({ 179 | $or: [ 180 | { from: address.toLowerCase() }, 181 | { to: address.toLowerCase() } 182 | ] 183 | }).sort({ blockNumber: -1 }); 184 | }; 185 | 186 | transactionSchema.statics.findPending = function() { 187 | return this.find({ status: 'pending' }).sort({ 'timestamps.submitted': 1 }); 188 | }; 189 | 190 | transactionSchema.statics.findFailed = function() { 191 | return this.find({ status: 'failed' }).sort({ 'timestamps.failed': -1 }); 192 | }; 193 | 194 | transactionSchema.statics.findRetryable = function() { 195 | return this.find({ 196 | status: 'failed', 197 | 'retry.attempts': { $lt: '$retry.maxAttempts' }, 198 | $or: [ 199 | { 'retry.nextRetry': { $lt: new Date() } }, 200 | { 'retry.nextRetry': { $exists: false } } 201 | ] 202 | }); 203 | }; 204 | 205 | transactionSchema.statics.getTransactionStats = function() { 206 | return this.aggregate([ 207 | { 208 | $group: { 209 | _id: null, 210 | totalTransactions: { $sum: 1 }, 211 | confirmedTransactions: { 212 | $sum: { $cond: [{ $eq: ['$status', 'confirmed'] }, 1, 0] } 213 | }, 214 | pendingTransactions: { 215 | $sum: { $cond: [{ $eq: ['$status', 'pending'] }, 1, 0] } 216 | }, 217 | failedTransactions: { 218 | $sum: { $cond: [{ $eq: ['$status', 'failed'] }, 1, 0] } 219 | }, 220 | totalGasUsed: { $sum: { $toDouble: '$gasUsed' } }, 221 | totalFees: { $sum: { $toDouble: '$fees.totalFee' } } 222 | } 223 | } 224 | ]); 225 | }; 226 | 227 | transactionSchema.statics.getNetworkStats = function() { 228 | return this.aggregate([ 229 | { 230 | $group: { 231 | _id: '$network', 232 | count: { $sum: 1 }, 233 | confirmed: { 234 | $sum: { $cond: [{ $eq: ['$status', 'confirmed'] }, 1, 0] } 235 | }, 236 | pending: { 237 | $sum: { $cond: [{ $eq: ['$status', 'pending'] }, 1, 0] } 238 | }, 239 | failed: { 240 | $sum: { $cond: [{ $eq: ['$status', 'failed'] }, 1, 0] } 241 | }, 242 | totalGasUsed: { $sum: { $toDouble: '$gasUsed' } }, 243 | totalFees: { $sum: { $toDouble: '$fees.totalFee' } } 244 | } 245 | }, 246 | { $sort: { count: -1 } } 247 | ]); 248 | }; 249 | 250 | transactionSchema.statics.getTypeStats = function() { 251 | return this.aggregate([ 252 | { 253 | $group: { 254 | _id: '$type', 255 | count: { $sum: 1 }, 256 | confirmed: { 257 | $sum: { $cond: [{ $eq: ['$status', 'confirmed'] }, 1, 0] } 258 | }, 259 | totalValue: { $sum: { $toDouble: '$value' } } 260 | } 261 | }, 262 | { $sort: { count: -1 } } 263 | ]); 264 | }; 265 | 266 | transactionSchema.statics.getRecentTransactions = function(limit = 50) { 267 | return this.find({ status: 'confirmed' }) 268 | .sort({ blockNumber: -1 }) 269 | .limit(limit) 270 | .populate('from', 'address username') 271 | .populate('to', 'address username'); 272 | }; 273 | 274 | transactionSchema.statics.cleanupOldTransactions = function(daysOld = 30) { 275 | const cutoffDate = new Date(Date.now() - daysOld * 24 * 60 * 60 * 1000); 276 | return this.deleteMany({ 277 | status: 'confirmed', 278 | 'timestamps.confirmed': { $lt: cutoffDate } 279 | }); 280 | }; 281 | 282 | const Transaction = mongoose.model('Transaction', transactionSchema); 283 | 284 | module.exports = Transaction; 285 | -------------------------------------------------------------------------------- /src/routes/contracts.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const BlockchainService = require('../services/BlockchainService'); 3 | 4 | const router = express.Router(); 5 | 6 | // Initialize blockchain service 7 | const blockchainService = new BlockchainService( 8 | process.env.ETHEREUM_RPC_URL, 9 | process.env.PRIVATE_KEY 10 | ); 11 | 12 | /** 13 | * POST /api/contracts/deploy 14 | * Deploy a smart contract 15 | */ 16 | router.post('/deploy', async (req, res) => { 17 | try { 18 | const { contractName, constructorArgs, options } = req.body; 19 | 20 | if (!contractName) { 21 | return res.status(400).json({ 22 | error: 'Contract name is required' 23 | }); 24 | } 25 | 26 | const deploymentResult = await blockchainService.deployContract( 27 | contractName, 28 | constructorArgs || [], 29 | options || {} 30 | ); 31 | 32 | res.status(201).json({ 33 | message: 'Contract deployed successfully', 34 | ...deploymentResult 35 | }); 36 | } catch (error) { 37 | res.status(500).json({ 38 | error: 'Contract deployment failed', 39 | message: error.message 40 | }); 41 | } 42 | }); 43 | 44 | /** 45 | * POST /api/contracts/load 46 | * Load an existing contract 47 | */ 48 | router.post('/load', async (req, res) => { 49 | try { 50 | const { contractName, address } = req.body; 51 | 52 | if (!contractName || !address) { 53 | return res.status(400).json({ 54 | error: 'Contract name and address are required' 55 | }); 56 | } 57 | 58 | const contract = await blockchainService.loadContract(contractName, address); 59 | 60 | res.json({ 61 | message: 'Contract loaded successfully', 62 | contractName: contractName, 63 | address: address, 64 | loadedAt: new Date().toISOString() 65 | }); 66 | } catch (error) { 67 | res.status(500).json({ 68 | error: 'Contract loading failed', 69 | message: error.message 70 | }); 71 | } 72 | }); 73 | 74 | /** 75 | * POST /api/contracts/:contractName/transaction 76 | * Send a transaction to a contract 77 | */ 78 | router.post('/:contractName/transaction', async (req, res) => { 79 | try { 80 | const { contractName } = req.params; 81 | const { method, args, options } = req.body; 82 | 83 | if (!method) { 84 | return res.status(400).json({ 85 | error: 'Method name is required' 86 | }); 87 | } 88 | 89 | const contract = blockchainService.getContract(contractName); 90 | const result = await blockchainService.sendTransaction( 91 | contract, 92 | method, 93 | args || [], 94 | options || {} 95 | ); 96 | 97 | res.json({ 98 | message: 'Transaction sent successfully', 99 | contractName: contractName, 100 | method: method, 101 | ...result 102 | }); 103 | } catch (error) { 104 | res.status(500).json({ 105 | error: 'Transaction failed', 106 | message: error.message 107 | }); 108 | } 109 | }); 110 | 111 | /** 112 | * POST /api/contracts/:contractName/call 113 | * Call a view function on a contract 114 | */ 115 | router.post('/:contractName/call', async (req, res) => { 116 | try { 117 | const { contractName } = req.params; 118 | const { method, args } = req.body; 119 | 120 | if (!method) { 121 | return res.status(400).json({ 122 | error: 'Method name is required' 123 | }); 124 | } 125 | 126 | const contract = blockchainService.getContract(contractName); 127 | const result = await blockchainService.callFunction( 128 | contract, 129 | method, 130 | args || [] 131 | ); 132 | 133 | res.json({ 134 | message: 'Function call successful', 135 | contractName: contractName, 136 | method: method, 137 | result: result 138 | }); 139 | } catch (error) { 140 | res.status(500).json({ 141 | error: 'Function call failed', 142 | message: error.message 143 | }); 144 | } 145 | }); 146 | 147 | /** 148 | * GET /api/contracts/:contractName/estimate-gas 149 | * Estimate gas for a transaction 150 | */ 151 | router.get('/:contractName/estimate-gas', async (req, res) => { 152 | try { 153 | const { contractName } = req.params; 154 | const { method, args, options } = req.query; 155 | 156 | if (!method) { 157 | return res.status(400).json({ 158 | error: 'Method name is required' 159 | }); 160 | } 161 | 162 | const contract = blockchainService.getContract(contractName); 163 | const gasEstimate = await blockchainService.estimateGas( 164 | contract, 165 | method, 166 | args ? JSON.parse(args) : [], 167 | options ? JSON.parse(options) : {} 168 | ); 169 | 170 | res.json({ 171 | contractName: contractName, 172 | method: method, 173 | gasEstimate: gasEstimate, 174 | estimatedAt: new Date().toISOString() 175 | }); 176 | } catch (error) { 177 | res.status(500).json({ 178 | error: 'Gas estimation failed', 179 | message: error.message 180 | }); 181 | } 182 | }); 183 | 184 | /** 185 | * GET /api/contracts/network-info 186 | * Get network information 187 | */ 188 | router.get('/network-info', async (req, res) => { 189 | try { 190 | const networkInfo = await blockchainService.getNetworkInfo(); 191 | 192 | res.json({ 193 | message: 'Network information retrieved successfully', 194 | ...networkInfo 195 | }); 196 | } catch (error) { 197 | res.status(500).json({ 198 | error: 'Failed to get network information', 199 | message: error.message 200 | }); 201 | } 202 | }); 203 | 204 | /** 205 | * GET /api/contracts/gas-price 206 | * Get current gas price 207 | */ 208 | router.get('/gas-price', async (req, res) => { 209 | try { 210 | const gasPrice = await blockchainService.getGasPrice(); 211 | 212 | res.json({ 213 | gasPrice: gasPrice, 214 | timestamp: new Date().toISOString() 215 | }); 216 | } catch (error) { 217 | res.status(500).json({ 218 | error: 'Failed to get gas price', 219 | message: error.message 220 | }); 221 | } 222 | }); 223 | 224 | /** 225 | * GET /api/contracts/balance/:address 226 | * Get account balance 227 | */ 228 | router.get('/balance/:address', async (req, res) => { 229 | try { 230 | const { address } = req.params; 231 | 232 | if (!address || !address.match(/^0x[a-fA-F0-9]{40}$/)) { 233 | return res.status(400).json({ 234 | error: 'Invalid Ethereum address' 235 | }); 236 | } 237 | 238 | const balance = await blockchainService.getBalance(address); 239 | 240 | res.json({ 241 | address: address, 242 | balance: balance, 243 | unit: 'ETH', 244 | timestamp: new Date().toISOString() 245 | }); 246 | } catch (error) { 247 | res.status(500).json({ 248 | error: 'Failed to get balance', 249 | message: error.message 250 | }); 251 | } 252 | }); 253 | 254 | /** 255 | * GET /api/contracts/transaction/:txHash 256 | * Get transaction details 257 | */ 258 | router.get('/transaction/:txHash', async (req, res) => { 259 | try { 260 | const { txHash } = req.params; 261 | 262 | if (!txHash || !txHash.match(/^0x[a-fA-F0-9]{64}$/)) { 263 | return res.status(400).json({ 264 | error: 'Invalid transaction hash' 265 | }); 266 | } 267 | 268 | const transaction = await blockchainService.getTransaction(txHash); 269 | 270 | res.json({ 271 | message: 'Transaction details retrieved successfully', 272 | ...transaction 273 | }); 274 | } catch (error) { 275 | res.status(500).json({ 276 | error: 'Failed to get transaction details', 277 | message: error.message 278 | }); 279 | } 280 | }); 281 | 282 | /** 283 | * POST /api/contracts/transaction/:txHash/wait 284 | * Wait for transaction confirmation 285 | */ 286 | router.post('/transaction/:txHash/wait', async (req, res) => { 287 | try { 288 | const { txHash } = req.params; 289 | const { confirmations } = req.body; 290 | 291 | if (!txHash || !txHash.match(/^0x[a-fA-F0-9]{64}$/)) { 292 | return res.status(400).json({ 293 | error: 'Invalid transaction hash' 294 | }); 295 | } 296 | 297 | const confirmation = await blockchainService.waitForConfirmation( 298 | txHash, 299 | confirmations || 1 300 | ); 301 | 302 | res.json({ 303 | message: 'Transaction confirmed', 304 | ...confirmation 305 | }); 306 | } catch (error) { 307 | res.status(500).json({ 308 | error: 'Transaction confirmation failed', 309 | message: error.message 310 | }); 311 | } 312 | }); 313 | 314 | module.exports = router; 315 | -------------------------------------------------------------------------------- /src/services/BlockchainService.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('ethers'); 2 | const fs = require('fs').promises; 3 | const path = require('path'); 4 | 5 | /** 6 | * Blockchain service for Ethereum interactions 7 | */ 8 | class BlockchainService { 9 | constructor(rpcUrl, privateKey) { 10 | this.provider = new ethers.JsonRpcProvider(rpcUrl); 11 | this.wallet = new ethers.Wallet(privateKey, this.provider); 12 | this.contracts = new Map(); 13 | } 14 | 15 | /** 16 | * Deploy a smart contract 17 | * @param {string} contractName - Name of the contract 18 | * @param {Array} constructorArgs - Constructor arguments 19 | * @param {Object} options - Deployment options 20 | * @returns {Promise} Deployment result 21 | */ 22 | async deployContract(contractName, constructorArgs = [], options = {}) { 23 | try { 24 | const contractPath = path.join(__dirname, '../contracts', `${contractName}.sol`); 25 | const contractSource = await fs.readFile(contractPath, 'utf8'); 26 | 27 | const factory = new ethers.ContractFactory( 28 | contractSource, 29 | this.wallet 30 | ); 31 | 32 | const contract = await factory.deploy(...constructorArgs, { 33 | gasLimit: options.gasLimit || 5000000, 34 | gasPrice: options.gasPrice 35 | }); 36 | 37 | await contract.waitForDeployment(); 38 | const address = await contract.getAddress(); 39 | 40 | this.contracts.set(contractName, { 41 | address: address, 42 | contract: contract, 43 | deployedAt: new Date().toISOString() 44 | }); 45 | 46 | return { 47 | contractName: contractName, 48 | address: address, 49 | transactionHash: contract.deploymentTransaction().hash, 50 | gasUsed: contract.deploymentTransaction().gasLimit, 51 | deployedAt: new Date().toISOString() 52 | }; 53 | } catch (error) { 54 | throw new Error(`Contract deployment failed: ${error.message}`); 55 | } 56 | } 57 | 58 | /** 59 | * Load an existing contract 60 | * @param {string} contractName - Name of the contract 61 | * @param {string} address - Contract address 62 | * @returns {Promise} Contract instance 63 | */ 64 | async loadContract(contractName, address) { 65 | try { 66 | const abiPath = path.join(__dirname, '../contracts', `${contractName}.json`); 67 | const abi = JSON.parse(await fs.readFile(abiPath, 'utf8')); 68 | 69 | const contract = new ethers.Contract(address, abi, this.wallet); 70 | 71 | this.contracts.set(contractName, { 72 | address: address, 73 | contract: contract, 74 | loadedAt: new Date().toISOString() 75 | }); 76 | 77 | return contract; 78 | } catch (error) { 79 | throw new Error(`Contract loading failed: ${error.message}`); 80 | } 81 | } 82 | 83 | /** 84 | * Get contract instance 85 | * @param {string} contractName - Name of the contract 86 | * @returns {Object} Contract instance 87 | */ 88 | getContract(contractName) { 89 | const contractData = this.contracts.get(contractName); 90 | if (!contractData) { 91 | throw new Error(`Contract ${contractName} not found`); 92 | } 93 | return contractData.contract; 94 | } 95 | 96 | /** 97 | * Send a transaction 98 | * @param {Object} contract - Contract instance 99 | * @param {string} method - Method name 100 | * @param {Array} args - Method arguments 101 | * @param {Object} options - Transaction options 102 | * @returns {Promise} Transaction result 103 | */ 104 | async sendTransaction(contract, method, args = [], options = {}) { 105 | try { 106 | const tx = await contract[method](...args, { 107 | gasLimit: options.gasLimit, 108 | gasPrice: options.gasPrice, 109 | value: options.value || 0 110 | }); 111 | 112 | const receipt = await tx.wait(); 113 | 114 | return { 115 | transactionHash: tx.hash, 116 | blockNumber: receipt.blockNumber, 117 | gasUsed: receipt.gasUsed.toString(), 118 | status: receipt.status, 119 | timestamp: new Date().toISOString() 120 | }; 121 | } catch (error) { 122 | throw new Error(`Transaction failed: ${error.message}`); 123 | } 124 | } 125 | 126 | /** 127 | * Call a view function 128 | * @param {Object} contract - Contract instance 129 | * @param {string} method - Method name 130 | * @param {Array} args - Method arguments 131 | * @returns {Promise} Function result 132 | */ 133 | async callFunction(contract, method, args = []) { 134 | try { 135 | return await contract[method](...args); 136 | } catch (error) { 137 | throw new Error(`Function call failed: ${error.message}`); 138 | } 139 | } 140 | 141 | /** 142 | * Get account balance 143 | * @param {string} address - Account address 144 | * @returns {Promise} Account balance in ETH 145 | */ 146 | async getBalance(address) { 147 | try { 148 | const balance = await this.provider.getBalance(address); 149 | return ethers.formatEther(balance); 150 | } catch (error) { 151 | throw new Error(`Failed to get balance: ${error.message}`); 152 | } 153 | } 154 | 155 | /** 156 | * Get transaction details 157 | * @param {string} txHash - Transaction hash 158 | * @returns {Promise} Transaction details 159 | */ 160 | async getTransaction(txHash) { 161 | try { 162 | const tx = await this.provider.getTransaction(txHash); 163 | const receipt = await this.provider.getTransactionReceipt(txHash); 164 | 165 | return { 166 | hash: tx.hash, 167 | from: tx.from, 168 | to: tx.to, 169 | value: ethers.formatEther(tx.value), 170 | gasLimit: tx.gasLimit.toString(), 171 | gasPrice: tx.gasPrice.toString(), 172 | nonce: tx.nonce, 173 | blockNumber: receipt.blockNumber, 174 | status: receipt.status, 175 | gasUsed: receipt.gasUsed.toString() 176 | }; 177 | } catch (error) { 178 | throw new Error(`Failed to get transaction: ${error.message}`); 179 | } 180 | } 181 | 182 | /** 183 | * Wait for transaction confirmation 184 | * @param {string} txHash - Transaction hash 185 | * @param {number} confirmations - Number of confirmations 186 | * @returns {Promise} Confirmation result 187 | */ 188 | async waitForConfirmation(txHash, confirmations = 1) { 189 | try { 190 | const receipt = await this.provider.waitForTransaction(txHash, confirmations); 191 | return { 192 | hash: txHash, 193 | confirmations: confirmations, 194 | blockNumber: receipt.blockNumber, 195 | status: receipt.status, 196 | confirmedAt: new Date().toISOString() 197 | }; 198 | } catch (error) { 199 | throw new Error(`Transaction confirmation failed: ${error.message}`); 200 | } 201 | } 202 | 203 | /** 204 | * Estimate gas for a transaction 205 | * @param {Object} contract - Contract instance 206 | * @param {string} method - Method name 207 | * @param {Array} args - Method arguments 208 | * @param {Object} options - Transaction options 209 | * @returns {Promise} Estimated gas 210 | */ 211 | async estimateGas(contract, method, args = [], options = {}) { 212 | try { 213 | const gasEstimate = await contract[method].estimateGas(...args, { 214 | value: options.value || 0 215 | }); 216 | return gasEstimate.toString(); 217 | } catch (error) { 218 | throw new Error(`Gas estimation failed: ${error.message}`); 219 | } 220 | } 221 | 222 | /** 223 | * Get current gas price 224 | * @returns {Promise} Current gas price 225 | */ 226 | async getGasPrice() { 227 | try { 228 | const gasPrice = await this.provider.getFeeData(); 229 | return gasPrice.gasPrice.toString(); 230 | } catch (error) { 231 | throw new Error(`Failed to get gas price: ${error.message}`); 232 | } 233 | } 234 | 235 | /** 236 | * Get network information 237 | * @returns {Promise} Network information 238 | */ 239 | async getNetworkInfo() { 240 | try { 241 | const network = await this.provider.getNetwork(); 242 | const blockNumber = await this.provider.getBlockNumber(); 243 | 244 | return { 245 | chainId: network.chainId.toString(), 246 | name: network.name, 247 | blockNumber: blockNumber, 248 | timestamp: new Date().toISOString() 249 | }; 250 | } catch (error) { 251 | throw new Error(`Failed to get network info: ${error.message}`); 252 | } 253 | } 254 | } 255 | 256 | module.exports = BlockchainService; 257 | -------------------------------------------------------------------------------- /src/routes/files.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const multer = require('multer'); 3 | const path = require('path'); 4 | const fs = require('fs').promises; 5 | 6 | const router = express.Router(); 7 | 8 | // Configure multer for file uploads 9 | const storage = multer.diskStorage({ 10 | destination: async (req, file, cb) => { 11 | const uploadDir = path.join(__dirname, '../../uploads'); 12 | try { 13 | await fs.mkdir(uploadDir, { recursive: true }); 14 | cb(null, uploadDir); 15 | } catch (error) { 16 | cb(error); 17 | } 18 | }, 19 | filename: (req, file, cb) => { 20 | const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); 21 | const ext = path.extname(file.originalname); 22 | cb(null, file.fieldname + '-' + uniqueSuffix + ext); 23 | } 24 | }); 25 | 26 | const upload = multer({ 27 | storage: storage, 28 | limits: { 29 | fileSize: 50 * 1024 * 1024, // 50MB limit 30 | files: 10 // Maximum 10 files 31 | }, 32 | fileFilter: (req, file, cb) => { 33 | // Allow all file types for now 34 | cb(null, true); 35 | } 36 | }); 37 | 38 | /** 39 | * POST /api/files/upload 40 | * Upload files to the server 41 | */ 42 | router.post('/upload', upload.array('files', 10), async (req, res) => { 43 | try { 44 | if (!req.files || req.files.length === 0) { 45 | return res.status(400).json({ 46 | error: 'No files uploaded' 47 | }); 48 | } 49 | 50 | const uploadedFiles = req.files.map(file => ({ 51 | filename: file.filename, 52 | originalName: file.originalname, 53 | mimetype: file.mimetype, 54 | size: file.size, 55 | path: file.path, 56 | uploadedAt: new Date().toISOString() 57 | })); 58 | 59 | res.status(201).json({ 60 | message: 'Files uploaded successfully', 61 | files: uploadedFiles, 62 | count: uploadedFiles.length 63 | }); 64 | } catch (error) { 65 | res.status(500).json({ 66 | error: 'File upload failed', 67 | message: error.message 68 | }); 69 | } 70 | }); 71 | 72 | /** 73 | * POST /api/files/upload-single 74 | * Upload a single file 75 | */ 76 | router.post('/upload-single', upload.single('file'), async (req, res) => { 77 | try { 78 | if (!req.file) { 79 | return res.status(400).json({ 80 | error: 'No file uploaded' 81 | }); 82 | } 83 | 84 | const uploadedFile = { 85 | filename: req.file.filename, 86 | originalName: req.file.originalname, 87 | mimetype: req.file.mimetype, 88 | size: req.file.size, 89 | path: req.file.path, 90 | uploadedAt: new Date().toISOString() 91 | }; 92 | 93 | res.status(201).json({ 94 | message: 'File uploaded successfully', 95 | file: uploadedFile 96 | }); 97 | } catch (error) { 98 | res.status(500).json({ 99 | error: 'File upload failed', 100 | message: error.message 101 | }); 102 | } 103 | }); 104 | 105 | /** 106 | * GET /api/files/:filename 107 | * Download a file 108 | */ 109 | router.get('/:filename', async (req, res) => { 110 | try { 111 | const filename = req.params.filename; 112 | const filePath = path.join(__dirname, '../../uploads', filename); 113 | 114 | // Check if file exists 115 | try { 116 | await fs.access(filePath); 117 | } catch (error) { 118 | return res.status(404).json({ 119 | error: 'File not found' 120 | }); 121 | } 122 | 123 | // Get file stats 124 | const stats = await fs.stat(filePath); 125 | 126 | // Set appropriate headers 127 | res.setHeader('Content-Type', 'application/octet-stream'); 128 | res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); 129 | res.setHeader('Content-Length', stats.size); 130 | 131 | // Stream the file 132 | const fileStream = require('fs').createReadStream(filePath); 133 | fileStream.pipe(res); 134 | } catch (error) { 135 | res.status(500).json({ 136 | error: 'File download failed', 137 | message: error.message 138 | }); 139 | } 140 | }); 141 | 142 | /** 143 | * GET /api/files/:filename/info 144 | * Get file information 145 | */ 146 | router.get('/:filename/info', async (req, res) => { 147 | try { 148 | const filename = req.params.filename; 149 | const filePath = path.join(__dirname, '../../uploads', filename); 150 | 151 | // Check if file exists 152 | try { 153 | await fs.access(filePath); 154 | } catch (error) { 155 | return res.status(404).json({ 156 | error: 'File not found' 157 | }); 158 | } 159 | 160 | // Get file stats 161 | const stats = await fs.stat(filePath); 162 | 163 | const fileInfo = { 164 | filename: filename, 165 | size: stats.size, 166 | createdAt: stats.birthtime, 167 | modifiedAt: stats.mtime, 168 | isFile: stats.isFile(), 169 | isDirectory: stats.isDirectory() 170 | }; 171 | 172 | res.json({ 173 | message: 'File information retrieved successfully', 174 | file: fileInfo 175 | }); 176 | } catch (error) { 177 | res.status(500).json({ 178 | error: 'Failed to get file information', 179 | message: error.message 180 | }); 181 | } 182 | }); 183 | 184 | /** 185 | * DELETE /api/files/:filename 186 | * Delete a file 187 | */ 188 | router.delete('/:filename', async (req, res) => { 189 | try { 190 | const filename = req.params.filename; 191 | const filePath = path.join(__dirname, '../../uploads', filename); 192 | 193 | // Check if file exists 194 | try { 195 | await fs.access(filePath); 196 | } catch (error) { 197 | return res.status(404).json({ 198 | error: 'File not found' 199 | }); 200 | } 201 | 202 | // Delete the file 203 | await fs.unlink(filePath); 204 | 205 | res.json({ 206 | message: 'File deleted successfully', 207 | filename: filename 208 | }); 209 | } catch (error) { 210 | res.status(500).json({ 211 | error: 'File deletion failed', 212 | message: error.message 213 | }); 214 | } 215 | }); 216 | 217 | /** 218 | * GET /api/files/list 219 | * List all uploaded files 220 | */ 221 | router.get('/list', async (req, res) => { 222 | try { 223 | const uploadDir = path.join(__dirname, '../../uploads'); 224 | 225 | // Check if upload directory exists 226 | try { 227 | await fs.access(uploadDir); 228 | } catch (error) { 229 | return res.json({ 230 | message: 'No files uploaded yet', 231 | files: [], 232 | count: 0 233 | }); 234 | } 235 | 236 | // Read directory contents 237 | const files = await fs.readdir(uploadDir); 238 | const fileList = []; 239 | 240 | for (const file of files) { 241 | const filePath = path.join(uploadDir, file); 242 | const stats = await fs.stat(filePath); 243 | 244 | if (stats.isFile()) { 245 | fileList.push({ 246 | filename: file, 247 | size: stats.size, 248 | createdAt: stats.birthtime, 249 | modifiedAt: stats.mtime 250 | }); 251 | } 252 | } 253 | 254 | res.json({ 255 | message: 'Files listed successfully', 256 | files: fileList, 257 | count: fileList.length 258 | }); 259 | } catch (error) { 260 | res.status(500).json({ 261 | error: 'Failed to list files', 262 | message: error.message 263 | }); 264 | } 265 | }); 266 | 267 | /** 268 | * POST /api/files/cleanup 269 | * Clean up old files 270 | */ 271 | router.post('/cleanup', async (req, res) => { 272 | try { 273 | const { olderThanDays = 30 } = req.body; 274 | const uploadDir = path.join(__dirname, '../../uploads'); 275 | const cutoffDate = new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1000); 276 | 277 | // Check if upload directory exists 278 | try { 279 | await fs.access(uploadDir); 280 | } catch (error) { 281 | return res.json({ 282 | message: 'No files to clean up', 283 | deletedCount: 0 284 | }); 285 | } 286 | 287 | // Read directory contents 288 | const files = await fs.readdir(uploadDir); 289 | let deletedCount = 0; 290 | 291 | for (const file of files) { 292 | const filePath = path.join(uploadDir, file); 293 | const stats = await fs.stat(filePath); 294 | 295 | if (stats.isFile() && stats.mtime < cutoffDate) { 296 | await fs.unlink(filePath); 297 | deletedCount++; 298 | } 299 | } 300 | 301 | res.json({ 302 | message: 'File cleanup completed', 303 | deletedCount: deletedCount, 304 | cutoffDate: cutoffDate 305 | }); 306 | } catch (error) { 307 | res.status(500).json({ 308 | error: 'File cleanup failed', 309 | message: error.message 310 | }); 311 | } 312 | }); 313 | 314 | module.exports = router; 315 | -------------------------------------------------------------------------------- /docs/API.md: -------------------------------------------------------------------------------- 1 | # ZeroData API Documentation 2 | 3 | ## Overview 4 | 5 | ZeroData is a decentralized data-sharing network powered by zero-knowledge proofs. This API provides comprehensive endpoints for data management, marketplace operations, identity management, multi-chain support, governance, and incentive systems. 6 | 7 | ## Base URL 8 | 9 | ``` 10 | https://api.zerodata.io 11 | ``` 12 | 13 | ## Authentication 14 | 15 | ZeroData uses JWT-based authentication with Ethereum signature verification. Include the JWT token in the Authorization header: 16 | 17 | ``` 18 | Authorization: Bearer 19 | ``` 20 | 21 | ## API Endpoints 22 | 23 | ### Authentication 24 | 25 | #### Register User 26 | ```http 27 | POST /api/auth/register 28 | ``` 29 | 30 | **Request Body:** 31 | ```json 32 | { 33 | "address": "0x1234567890123456789012345678901234567890", 34 | "signature": "0x...", 35 | "message": "ZeroData Authentication\nAddress: 0x...\nTimestamp: 1692000000" 36 | } 37 | ``` 38 | 39 | **Response:** 40 | ```json 41 | { 42 | "message": "User registered successfully", 43 | "token": "jwt_token", 44 | "user": { 45 | "address": "0x...", 46 | "registeredAt": "2023-07-15T10:30:00Z" 47 | } 48 | } 49 | ``` 50 | 51 | #### Login User 52 | ```http 53 | POST /api/auth/login 54 | ``` 55 | 56 | #### Get User Profile 57 | ```http 58 | GET /api/auth/profile 59 | ``` 60 | 61 | ### Data Management 62 | 63 | #### Upload Data 64 | ```http 65 | POST /api/data/upload 66 | ``` 67 | 68 | **Request Body:** 69 | ```json 70 | { 71 | "data": "sensitive data", 72 | "accessPolicy": "private", 73 | "price": "1000000000000000000", 74 | "description": "Data description" 75 | } 76 | ``` 77 | 78 | **Response:** 79 | ```json 80 | { 81 | "dataHash": "0x...", 82 | "ipfsHash": "Qm...", 83 | "accessPolicy": "private", 84 | "price": "1000000000000000000", 85 | "owner": "0x...", 86 | "proof": {...}, 87 | "uploadedAt": "2023-07-15T10:30:00Z" 88 | } 89 | ``` 90 | 91 | #### Get Data Metadata 92 | ```http 93 | GET /api/data/:dataHash 94 | ``` 95 | 96 | #### Request Data Download 97 | ```http 98 | POST /api/data/:dataHash/download 99 | ``` 100 | 101 | #### Verify Data Integrity 102 | ```http 103 | POST /api/data/:dataHash/verify 104 | ``` 105 | 106 | ### Marketplace 107 | 108 | #### List Data for Sale 109 | ```http 110 | POST /api/market/list 111 | ``` 112 | 113 | **Request Body:** 114 | ```json 115 | { 116 | "dataHash": "0x...", 117 | "price": "1000000000000000000", 118 | "description": "Marketplace listing", 119 | "category": "finance", 120 | "tags": ["crypto", "trading"] 121 | } 122 | ``` 123 | 124 | #### Browse Marketplace 125 | ```http 126 | GET /api/market/browse 127 | ``` 128 | 129 | **Query Parameters:** 130 | - `category`: Filter by category 131 | - `minPrice`: Minimum price filter 132 | - `maxPrice`: Maximum price filter 133 | - `tags`: Comma-separated tags 134 | - `sortBy`: Sort field (price, date, views, purchases) 135 | - `order`: Sort order (asc, desc) 136 | - `page`: Page number 137 | - `limit`: Items per page 138 | 139 | #### Purchase Data 140 | ```http 141 | POST /api/market/purchase 142 | ``` 143 | 144 | **Request Body:** 145 | ```json 146 | { 147 | "dataHash": "0x...", 148 | "sellerAddress": "0x..." 149 | } 150 | ``` 151 | 152 | #### Get Categories 153 | ```http 154 | GET /api/market/categories 155 | ``` 156 | 157 | #### Get Marketplace Statistics 158 | ```http 159 | GET /api/market/stats 160 | ``` 161 | 162 | ### Decentralized Identity (DID) 163 | 164 | #### Create DID 165 | ```http 166 | POST /api/did/create 167 | ``` 168 | 169 | **Request Body:** 170 | ```json 171 | { 172 | "address": "0x1234567890123456789012345678901234567890" 173 | } 174 | ``` 175 | 176 | #### Resolve DID 177 | ```http 178 | GET /api/did/:did 179 | ``` 180 | 181 | #### Create Verifiable Credential 182 | ```http 183 | POST /api/did/credential/create 184 | ``` 185 | 186 | **Request Body:** 187 | ```json 188 | { 189 | "issuerDID": "did:zerodata:0x...", 190 | "holderDID": "did:zerodata:0x...", 191 | "credentialData": { 192 | "dataProviderLevel": "verified", 193 | "reputationScore": 95 194 | }, 195 | "privateKey": "0x..." 196 | } 197 | ``` 198 | 199 | #### Verify Credential 200 | ```http 201 | POST /api/did/credential/verify 202 | ``` 203 | 204 | #### Create Verifiable Presentation 205 | ```http 206 | POST /api/did/presentation/create 207 | ``` 208 | 209 | #### Verify Presentation 210 | ```http 211 | POST /api/did/presentation/verify 212 | ``` 213 | 214 | ### Multi-chain Support 215 | 216 | #### Get Supported Networks 217 | ```http 218 | GET /api/chains/networks 219 | ``` 220 | 221 | #### Get Network Information 222 | ```http 223 | GET /api/chains/networks/:networkName 224 | ``` 225 | 226 | #### Deploy Contract 227 | ```http 228 | POST /api/chains/deploy 229 | ``` 230 | 231 | **Request Body:** 232 | ```json 233 | { 234 | "networkName": "ethereum", 235 | "contractName": "DataRegistry", 236 | "constructorArgs": [], 237 | "options": {} 238 | } 239 | ``` 240 | 241 | #### Send Transaction 242 | ```http 243 | POST /api/chains/transaction 244 | ``` 245 | 246 | #### Get Balance 247 | ```http 248 | GET /api/chains/balance/:networkName/:address 249 | ``` 250 | 251 | #### Sync Data Across Chains 252 | ```http 253 | POST /api/chains/sync 254 | ``` 255 | 256 | **Request Body:** 257 | ```json 258 | { 259 | "sourceNetwork": "ethereum", 260 | "targetNetwork": "polygon", 261 | "dataHash": "0x..." 262 | } 263 | ``` 264 | 265 | #### Get Cross-chain Status 266 | ```http 267 | GET /api/chains/status/:dataHash 268 | ``` 269 | 270 | ### DAO Governance 271 | 272 | #### Create Proposal 273 | ```http 274 | POST /api/dao/propose 275 | ``` 276 | 277 | **Request Body:** 278 | ```json 279 | { 280 | "targets": ["0x..."], 281 | "values": ["0"], 282 | "signatures": ["updateRewardRate(uint256)"], 283 | "calldatas": ["0x..."], 284 | "title": "Update reward rate", 285 | "description": "Proposal to increase data provider rewards", 286 | "proposalType": "parameter" 287 | } 288 | ``` 289 | 290 | #### Cast Vote 291 | ```http 292 | POST /api/dao/vote 293 | ``` 294 | 295 | **Request Body:** 296 | ```json 297 | { 298 | "proposalId": 1, 299 | "support": 1 300 | } 301 | ``` 302 | 303 | **Support Values:** 304 | - `0`: Abstain 305 | - `1`: For 306 | - `2`: Against 307 | 308 | #### Execute Proposal 309 | ```http 310 | POST /api/dao/execute 311 | ``` 312 | 313 | #### Get Proposals 314 | ```http 315 | GET /api/dao/proposals 316 | ``` 317 | 318 | **Query Parameters:** 319 | - `page`: Page number 320 | - `limit`: Items per page 321 | - `status`: Filter by status (pending, active, canceled, defeated, succeeded, executed) 322 | 323 | #### Get Proposal Details 324 | ```http 325 | GET /api/dao/proposals/:proposalId 326 | ``` 327 | 328 | #### Get Governance Parameters 329 | ```http 330 | GET /api/dao/governance-parameters 331 | ``` 332 | 333 | ### Incentive System 334 | 335 | #### Stake Tokens 336 | ```http 337 | POST /api/incentives/stake 338 | ``` 339 | 340 | **Request Body:** 341 | ```json 342 | { 343 | "pool": "dataProvider", 344 | "amount": "1000000000000000000" 345 | } 346 | ``` 347 | 348 | #### Unstake Tokens 349 | ```http 350 | POST /api/incentives/unstake 351 | ``` 352 | 353 | #### Claim Rewards 354 | ```http 355 | POST /api/incentives/claim-rewards 356 | ``` 357 | 358 | #### Get Staking Pools 359 | ```http 360 | GET /api/incentives/pools 361 | ``` 362 | 363 | #### Get User Staking Info 364 | ```http 365 | GET /api/incentives/user/:address/staking 366 | ``` 367 | 368 | #### Get Vesting Info 369 | ```http 370 | GET /api/incentives/user/:address/vesting 371 | ``` 372 | 373 | #### Claim Vested Tokens 374 | ```http 375 | POST /api/incentives/claim-vesting 376 | ``` 377 | 378 | ## Error Responses 379 | 380 | All error responses follow this format: 381 | 382 | ```json 383 | { 384 | "error": "Error message", 385 | "message": "Detailed error description" 386 | } 387 | ``` 388 | 389 | ### Common HTTP Status Codes 390 | 391 | - `200`: Success 392 | - `201`: Created 393 | - `400`: Bad Request 394 | - `401`: Unauthorized 395 | - `403`: Forbidden 396 | - `404`: Not Found 397 | - `409`: Conflict 398 | - `500`: Internal Server Error 399 | 400 | ## Rate Limiting 401 | 402 | API requests are rate limited to 100 requests per 15-minute window per IP address. 403 | 404 | ## Webhooks 405 | 406 | ZeroData supports webhooks for real-time notifications: 407 | 408 | ### Webhook Events 409 | 410 | - `data.uploaded`: Data uploaded successfully 411 | - `data.purchased`: Data purchased 412 | - `proposal.created`: New governance proposal 413 | - `proposal.executed`: Proposal executed 414 | - `vote.cast`: Vote cast on proposal 415 | 416 | ### Webhook Payload 417 | 418 | ```json 419 | { 420 | "event": "data.uploaded", 421 | "data": { 422 | "dataHash": "0x...", 423 | "owner": "0x...", 424 | "timestamp": "2023-07-15T10:30:00Z" 425 | }, 426 | "timestamp": "2023-07-15T10:30:00Z" 427 | } 428 | ``` 429 | 430 | ## SDKs 431 | 432 | ZeroData provides SDKs for multiple programming languages: 433 | 434 | - **JavaScript/TypeScript**: `npm install zerodata-sdk` 435 | - **Python**: `pip install zerodata-sdk` 436 | - **Go**: `go get github.com/zerodata/sdk-go` 437 | 438 | ### JavaScript SDK Example 439 | 440 | ```javascript 441 | const { ZeroDataClient } = require('zerodata-sdk'); 442 | 443 | const client = new ZeroDataClient({ 444 | rpcUrl: 'https://mainnet.infura.io/v3/YOUR_PROJECT_ID', 445 | privateKey: '0x...' 446 | }); 447 | 448 | // Upload data 449 | const result = await client.uploadData({ 450 | data: "sensitive information", 451 | accessPolicy: "private", 452 | price: "1000000000000000000" 453 | }); 454 | 455 | // Generate proof 456 | const proof = await client.generateProof({ 457 | dataHash: result.dataHash, 458 | ownerAddress: "0x...", 459 | conditions: { accessPolicy: "private" } 460 | }); 461 | ``` 462 | 463 | ## Support 464 | 465 | For API support and questions: 466 | 467 | - **Documentation**: [docs.zerodata.io](https://docs.zerodata.io) 468 | - **Discord**: [discord.gg/zerodata](https://discord.gg/zerodata) 469 | - **GitHub**: [github.com/zerodata](https://github.com/zerodata) 470 | - **Email**: support@zerodata.io 471 | -------------------------------------------------------------------------------- /src/utils/CryptoUtils.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const fs = require('fs').promises; 3 | const path = require('path'); 4 | 5 | /** 6 | * Advanced cryptographic utilities for ZeroData 7 | */ 8 | class CryptoUtils { 9 | constructor() { 10 | this.supportedAlgorithms = ['aes-256-gcm', 'aes-256-cbc', 'aes-192-gcm']; 11 | this.hashAlgorithms = ['sha256', 'sha512', 'blake2b512']; 12 | } 13 | 14 | /** 15 | * Generate secure random bytes 16 | * @param {number} length - Length in bytes 17 | * @returns {Buffer} Random bytes 18 | */ 19 | generateRandomBytes(length) { 20 | return crypto.randomBytes(length); 21 | } 22 | 23 | /** 24 | * Generate secure random string 25 | * @param {number} length - Length of string 26 | * @param {string} charset - Character set to use 27 | * @returns {string} Random string 28 | */ 29 | generateRandomString(length, charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') { 30 | let result = ''; 31 | for (let i = 0; i < length; i++) { 32 | result += charset.charAt(Math.floor(Math.random() * charset.length)); 33 | } 34 | return result; 35 | } 36 | 37 | /** 38 | * Generate key pair for asymmetric encryption 39 | * @param {string} type - Key type (rsa, ec) 40 | * @param {Object} options - Key generation options 41 | * @returns {Object} Key pair 42 | */ 43 | generateKeyPair(type = 'rsa', options = {}) { 44 | const defaultOptions = { 45 | modulusLength: 2048, 46 | publicKeyEncoding: { type: 'spki', format: 'pem' }, 47 | privateKeyEncoding: { type: 'pkcs8', format: 'pem' } 48 | }; 49 | 50 | const keyOptions = { ...defaultOptions, ...options }; 51 | 52 | return crypto.generateKeyPairSync(type, keyOptions); 53 | } 54 | 55 | /** 56 | * Encrypt data with public key 57 | * @param {string} data - Data to encrypt 58 | * @param {string} publicKey - Public key in PEM format 59 | * @returns {string} Encrypted data 60 | */ 61 | encryptWithPublicKey(data, publicKey) { 62 | const encrypted = crypto.publicEncrypt(publicKey, Buffer.from(data, 'utf8')); 63 | return encrypted.toString('base64'); 64 | } 65 | 66 | /** 67 | * Decrypt data with private key 68 | * @param {string} encryptedData - Encrypted data 69 | * @param {string} privateKey - Private key in PEM format 70 | * @returns {string} Decrypted data 71 | */ 72 | decryptWithPrivateKey(encryptedData, privateKey) { 73 | const decrypted = crypto.privateDecrypt(privateKey, Buffer.from(encryptedData, 'base64')); 74 | return decrypted.toString('utf8'); 75 | } 76 | 77 | /** 78 | * Create digital signature 79 | * @param {string} data - Data to sign 80 | * @param {string} privateKey - Private key 81 | * @param {string} algorithm - Signature algorithm 82 | * @returns {string} Digital signature 83 | */ 84 | createSignature(data, privateKey, algorithm = 'sha256') { 85 | const sign = crypto.createSign(algorithm); 86 | sign.update(data); 87 | return sign.sign(privateKey, 'hex'); 88 | } 89 | 90 | /** 91 | * Verify digital signature 92 | * @param {string} data - Original data 93 | * @param {string} signature - Digital signature 94 | * @param {string} publicKey - Public key 95 | * @param {string} algorithm - Signature algorithm 96 | * @returns {boolean} True if signature is valid 97 | */ 98 | verifySignature(data, signature, publicKey, algorithm = 'sha256') { 99 | try { 100 | const verify = crypto.createVerify(algorithm); 101 | verify.update(data); 102 | return verify.verify(publicKey, signature, 'hex'); 103 | } catch (error) { 104 | return false; 105 | } 106 | } 107 | 108 | /** 109 | * Generate Merkle tree root 110 | * @param {Array} data - Array of data items 111 | * @param {string} algorithm - Hash algorithm 112 | * @returns {string} Merkle root 113 | */ 114 | generateMerkleRoot(data, algorithm = 'sha256') { 115 | if (data.length === 0) return ''; 116 | if (data.length === 1) return crypto.createHash(algorithm).update(data[0]).digest('hex'); 117 | 118 | const nextLevel = []; 119 | for (let i = 0; i < data.length; i += 2) { 120 | const left = data[i]; 121 | const right = i + 1 < data.length ? data[i + 1] : data[i]; 122 | const combined = crypto.createHash(algorithm).update(left + right).digest('hex'); 123 | nextLevel.push(combined); 124 | } 125 | 126 | return this.generateMerkleRoot(nextLevel, algorithm); 127 | } 128 | 129 | /** 130 | * Generate Merkle proof 131 | * @param {Array} data - Array of data items 132 | * @param {number} index - Index of item to prove 133 | * @param {string} algorithm - Hash algorithm 134 | * @returns {Array} Merkle proof 135 | */ 136 | generateMerkleProof(data, index, algorithm = 'sha256') { 137 | const proof = []; 138 | let currentLevel = data.map(item => crypto.createHash(algorithm).update(item).digest('hex')); 139 | let currentIndex = index; 140 | 141 | while (currentLevel.length > 1) { 142 | const nextLevel = []; 143 | for (let i = 0; i < currentLevel.length; i += 2) { 144 | const left = currentLevel[i]; 145 | const right = i + 1 < currentLevel.length ? currentLevel[i + 1] : currentLevel[i]; 146 | const combined = crypto.createHash(algorithm).update(left + right).digest('hex'); 147 | nextLevel.push(combined); 148 | 149 | if (i === currentIndex || i + 1 === currentIndex) { 150 | proof.push(i === currentIndex ? right : left); 151 | } 152 | } 153 | currentLevel = nextLevel; 154 | currentIndex = Math.floor(currentIndex / 2); 155 | } 156 | 157 | return proof; 158 | } 159 | 160 | /** 161 | * Verify Merkle proof 162 | * @param {string} leaf - Leaf data 163 | * @param {Array} proof - Merkle proof 164 | * @param {string} root - Merkle root 165 | * @param {string} algorithm - Hash algorithm 166 | * @returns {boolean} True if proof is valid 167 | */ 168 | verifyMerkleProof(leaf, proof, root, algorithm = 'sha256') { 169 | let hash = crypto.createHash(algorithm).update(leaf).digest('hex'); 170 | 171 | for (const proofElement of proof) { 172 | hash = crypto.createHash(algorithm).update(hash + proofElement).digest('hex'); 173 | } 174 | 175 | return hash === root; 176 | } 177 | 178 | /** 179 | * Generate PBKDF2 key from password 180 | * @param {string} password - Password 181 | * @param {string} salt - Salt 182 | * @param {number} iterations - Iterations 183 | * @param {number} keyLength - Key length in bytes 184 | * @param {string} algorithm - Hash algorithm 185 | * @returns {Buffer} Derived key 186 | */ 187 | generatePBKDF2Key(password, salt, iterations = 100000, keyLength = 32, algorithm = 'sha256') { 188 | return crypto.pbkdf2Sync(password, salt, iterations, keyLength, algorithm); 189 | } 190 | 191 | /** 192 | * Generate scrypt key from password 193 | * @param {string} password - Password 194 | * @param {string} salt - Salt 195 | * @param {number} keyLength - Key length in bytes 196 | * @param {Object} options - Scrypt options 197 | * @returns {Buffer} Derived key 198 | */ 199 | generateScryptKey(password, salt, keyLength = 32, options = {}) { 200 | const defaultOptions = { 201 | N: 16384, 202 | r: 8, 203 | p: 1, 204 | maxmem: 32 * 1024 * 1024 205 | }; 206 | 207 | const scryptOptions = { ...defaultOptions, ...options }; 208 | return crypto.scryptSync(password, salt, keyLength, scryptOptions); 209 | } 210 | 211 | /** 212 | * Generate Argon2 key from password 213 | * @param {string} password - Password 214 | * @param {string} salt - Salt 215 | * @param {Object} options - Argon2 options 216 | * @returns {Promise} Derived key 217 | */ 218 | async generateArgon2Key(password, salt, options = {}) { 219 | const argon2 = require('argon2'); 220 | 221 | const defaultOptions = { 222 | type: argon2.argon2id, 223 | memoryCost: 2 ** 16, 224 | timeCost: 3, 225 | parallelism: 1, 226 | hashLength: 32 227 | }; 228 | 229 | const argon2Options = { ...defaultOptions, ...options }; 230 | return await argon2.hash(password, { ...argon2Options, salt }); 231 | } 232 | 233 | /** 234 | * Generate secure token 235 | * @param {number} length - Token length 236 | * @param {string} type - Token type (hex, base64, url-safe) 237 | * @returns {string} Secure token 238 | */ 239 | generateSecureToken(length = 32, type = 'hex') { 240 | const randomBytes = this.generateRandomBytes(length); 241 | 242 | switch (type) { 243 | case 'hex': 244 | return randomBytes.toString('hex'); 245 | case 'base64': 246 | return randomBytes.toString('base64'); 247 | case 'url-safe': 248 | return randomBytes.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); 249 | default: 250 | return randomBytes.toString('hex'); 251 | } 252 | } 253 | 254 | /** 255 | * Constant time string comparison 256 | * @param {string} a - First string 257 | * @param {string} b - Second string 258 | * @returns {boolean} True if strings are equal 259 | */ 260 | constantTimeCompare(a, b) { 261 | return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)); 262 | } 263 | 264 | /** 265 | * Generate UUID v4 266 | * @returns {string} UUID v4 267 | */ 268 | generateUUID() { 269 | return crypto.randomUUID(); 270 | } 271 | 272 | /** 273 | * Generate cryptographically secure random integer 274 | * @param {number} min - Minimum value 275 | * @param {number} max - Maximum value 276 | * @returns {number} Random integer 277 | */ 278 | generateSecureRandomInt(min, max) { 279 | const range = max - min + 1; 280 | const maxValidValue = Math.floor(0x100000000 / range) * range - 1; 281 | 282 | let randomValue; 283 | do { 284 | randomValue = crypto.randomBytes(4).readUInt32BE(0); 285 | } while (randomValue > maxValidValue); 286 | 287 | return min + (randomValue % range); 288 | } 289 | } 290 | 291 | module.exports = CryptoUtils; 292 | -------------------------------------------------------------------------------- /src/utils/CircuitUtils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const snarkjs = require('snarkjs'); 4 | const circomlib = require('circomlib'); 5 | 6 | /** 7 | * Circuit compilation and setup utilities 8 | */ 9 | class CircuitUtils { 10 | constructor() { 11 | this.circuitsPath = path.join(__dirname, '../circuits'); 12 | this.buildPath = path.join(__dirname, '../build'); 13 | this.setupPath = path.join(__dirname, '../setup'); 14 | } 15 | 16 | /** 17 | * Compile a Circom circuit 18 | * @param {string} circuitName - Name of the circuit file 19 | * @returns {Promise} Compilation result 20 | */ 21 | async compileCircuit(circuitName) { 22 | try { 23 | const circuitPath = path.join(this.circuitsPath, `${circuitName}.circom`); 24 | const buildPath = path.join(this.buildPath, circuitName); 25 | 26 | // Create build directory if it doesn't exist 27 | if (!fs.existsSync(this.buildPath)) { 28 | fs.mkdirSync(this.buildPath, { recursive: true }); 29 | } 30 | 31 | // Compile circuit using snarkjs 32 | const circuit = await snarkjs.circom.compile(circuitPath, { 33 | output: buildPath, 34 | verbose: true 35 | }); 36 | 37 | return { 38 | circuit: circuit, 39 | r1cs: circuit.r1cs, 40 | wasm: circuit.wasm, 41 | sym: circuit.sym 42 | }; 43 | } catch (error) { 44 | throw new Error(`Circuit compilation failed: ${error.message}`); 45 | } 46 | } 47 | 48 | /** 49 | * Generate trusted setup for a circuit 50 | * @param {string} circuitName - Name of the circuit 51 | * @param {number} power - Power of 2 for the ceremony size 52 | * @returns {Promise} Setup result 53 | */ 54 | async generateTrustedSetup(circuitName, power = 20) { 55 | try { 56 | const buildPath = path.join(this.buildPath, circuitName); 57 | const setupPath = path.join(this.setupPath, circuitName); 58 | 59 | // Create setup directory if it doesn't exist 60 | if (!fs.existsSync(this.setupPath)) { 61 | fs.mkdirSync(this.setupPath, { recursive: true }); 62 | } 63 | 64 | // Generate random beacon 65 | const beacon = await snarkjs.powersOfTau.newAccumulator(power); 66 | 67 | // Generate proving key 68 | const provingKey = await snarkjs.zKey.newZKey( 69 | path.join(buildPath, `${circuitName}.r1cs`), 70 | beacon, 71 | path.join(setupPath, `${circuitName}_proving_key.zkey`) 72 | ); 73 | 74 | // Generate verification key 75 | const verificationKey = await snarkjs.zKey.exportVerificationKey( 76 | path.join(setupPath, `${circuitName}_proving_key.zkey`) 77 | ); 78 | 79 | // Save verification key 80 | fs.writeFileSync( 81 | path.join(setupPath, `${circuitName}_verification_key.json`), 82 | JSON.stringify(verificationKey, null, 2) 83 | ); 84 | 85 | return { 86 | provingKey: path.join(setupPath, `${circuitName}_proving_key.zkey`), 87 | verificationKey: path.join(setupPath, `${circuitName}_verification_key.json`), 88 | beacon: beacon 89 | }; 90 | } catch (error) { 91 | throw new Error(`Trusted setup failed: ${error.message}`); 92 | } 93 | } 94 | 95 | /** 96 | * Generate witness for circuit inputs 97 | * @param {string} circuitName - Name of the circuit 98 | * @param {Object} inputs - Circuit inputs 99 | * @returns {Promise} Generated witness 100 | */ 101 | async generateWitness(circuitName, inputs) { 102 | try { 103 | const buildPath = path.join(this.buildPath, circuitName); 104 | const witnessPath = path.join(this.buildPath, `${circuitName}_witness.wtns`); 105 | 106 | // Generate witness 107 | const witness = await snarkjs.wtns.calculate( 108 | inputs, 109 | path.join(buildPath, `${circuitName}.wasm`), 110 | witnessPath 111 | ); 112 | 113 | return witness; 114 | } catch (error) { 115 | throw new Error(`Witness generation failed: ${error.message}`); 116 | } 117 | } 118 | 119 | /** 120 | * Generate proof using Groth16 121 | * @param {string} circuitName - Name of the circuit 122 | * @param {Object} inputs - Circuit inputs 123 | * @returns {Promise} Generated proof 124 | */ 125 | async generateProof(circuitName, inputs) { 126 | try { 127 | const setupPath = path.join(this.setupPath, circuitName); 128 | const buildPath = path.join(this.buildPath, circuitName); 129 | 130 | // Generate witness 131 | const witness = await this.generateWitness(circuitName, inputs); 132 | 133 | // Generate proof 134 | const { proof, publicSignals } = await snarkjs.groth16.prove( 135 | path.join(setupPath, `${circuitName}_proving_key.zkey`), 136 | witness 137 | ); 138 | 139 | return { 140 | proof: proof, 141 | publicSignals: publicSignals, 142 | circuitName: circuitName, 143 | timestamp: new Date().toISOString() 144 | }; 145 | } catch (error) { 146 | throw new Error(`Proof generation failed: ${error.message}`); 147 | } 148 | } 149 | 150 | /** 151 | * Verify proof using Groth16 152 | * @param {string} circuitName - Name of the circuit 153 | * @param {Object} proof - Proof to verify 154 | * @param {Array} publicSignals - Public signals 155 | * @returns {Promise} True if proof is valid 156 | */ 157 | async verifyProof(circuitName, proof, publicSignals) { 158 | try { 159 | const setupPath = path.join(this.setupPath, circuitName); 160 | const verificationKey = JSON.parse( 161 | fs.readFileSync(path.join(setupPath, `${circuitName}_verification_key.json`), 'utf8') 162 | ); 163 | 164 | const isValid = await snarkjs.groth16.verify( 165 | verificationKey, 166 | publicSignals, 167 | proof 168 | ); 169 | 170 | return isValid; 171 | } catch (error) { 172 | throw new Error(`Proof verification failed: ${error.message}`); 173 | } 174 | } 175 | 176 | /** 177 | * Convert Ethereum address to circuit input format 178 | * @param {string} address - Ethereum address 179 | * @returns {Array} Address as 8 32-bit chunks 180 | */ 181 | addressToCircuitInput(address) { 182 | // Remove 0x prefix and convert to bytes 183 | const addressBytes = Buffer.from(address.slice(2), 'hex'); 184 | 185 | // Pad to 32 bytes 186 | const paddedAddress = Buffer.alloc(32); 187 | addressBytes.copy(paddedAddress, 32 - addressBytes.length); 188 | 189 | // Split into 8 32-bit chunks 190 | const chunks = []; 191 | for (let i = 0; i < 8; i++) { 192 | chunks.push(paddedAddress.readUInt32BE(i * 4)); 193 | } 194 | 195 | return chunks; 196 | } 197 | 198 | /** 199 | * Convert data hash to circuit input format 200 | * @param {string} hash - Data hash 201 | * @returns {Array} Hash as 8 32-bit chunks 202 | */ 203 | hashToCircuitInput(hash) { 204 | // Remove 0x prefix if present 205 | const cleanHash = hash.startsWith('0x') ? hash.slice(2) : hash; 206 | 207 | // Convert to bytes 208 | const hashBytes = Buffer.from(cleanHash, 'hex'); 209 | 210 | // Pad to 32 bytes 211 | const paddedHash = Buffer.alloc(32); 212 | hashBytes.copy(paddedHash, 32 - hashBytes.length); 213 | 214 | // Split into 8 32-bit chunks 215 | const chunks = []; 216 | for (let i = 0; i < 8; i++) { 217 | chunks.push(paddedHash.readUInt32BE(i * 4)); 218 | } 219 | 220 | return chunks; 221 | } 222 | 223 | /** 224 | * Convert timestamp to circuit input format 225 | * @param {number} timestamp - Unix timestamp 226 | * @returns {number} Timestamp as 32-bit integer 227 | */ 228 | timestampToCircuitInput(timestamp) { 229 | return Math.floor(timestamp / 1000); // Convert to seconds 230 | } 231 | 232 | /** 233 | * Generate random circuit inputs for testing 234 | * @param {string} circuitName - Name of the circuit 235 | * @returns {Object} Random inputs 236 | */ 237 | generateRandomInputs(circuitName) { 238 | const inputs = {}; 239 | 240 | switch (circuitName) { 241 | case 'dataVerification': 242 | inputs.dataHash = this.hashToCircuitInput('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'); 243 | inputs.ownerAddress = this.addressToCircuitInput('0x1234567890123456789012345678901234567890'); 244 | inputs.timestamp = this.timestampToCircuitInput(Date.now()); 245 | inputs.accessPolicy = Math.floor(Math.random() * 1000000); 246 | inputs.price = Math.floor(Math.random() * 1000000000000000000); 247 | inputs.secretData = Array.from({ length: 8 }, () => Math.floor(Math.random() * 4294967296)); 248 | inputs.secretKey = Array.from({ length: 8 }, () => Math.floor(Math.random() * 4294967296)); 249 | break; 250 | 251 | case 'accessControl': 252 | inputs.requesterAddress = this.addressToCircuitInput('0xabcdef1234567890abcdef1234567890abcdef12'); 253 | inputs.dataHash = this.hashToCircuitInput('0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890'); 254 | inputs.permissions = Array.from({ length: 4 }, () => Math.floor(Math.random() * 16)); 255 | inputs.timestamp = this.timestampToCircuitInput(Date.now()); 256 | inputs.secretIdentity = Array.from({ length: 8 }, () => Math.floor(Math.random() * 4294967296)); 257 | inputs.accessToken = Array.from({ length: 8 }, () => Math.floor(Math.random() * 4294967296)); 258 | inputs.dataKey = Array.from({ length: 8 }, () => Math.floor(Math.random() * 4294967296)); 259 | break; 260 | 261 | case 'dataIntegrity': 262 | inputs.originalHash = this.hashToCircuitInput('0x1111111111111111111111111111111111111111111111111111111111111111'); 263 | inputs.currentHash = this.hashToCircuitInput('0x1111111111111111111111111111111111111111111111111111111111111111'); 264 | inputs.timestamp = this.timestampToCircuitInput(Date.now()); 265 | inputs.dataChunks = Array.from({ length: 32 }, () => Math.floor(Math.random() * 4294967296)); 266 | inputs.merkleRoot = Math.floor(Math.random() * 4294967296); 267 | inputs.merkleProof = Array.from({ length: 8 }, () => Math.floor(Math.random() * 4294967296)); 268 | break; 269 | } 270 | 271 | return inputs; 272 | } 273 | } 274 | 275 | module.exports = CircuitUtils; 276 | -------------------------------------------------------------------------------- /src/tests/services.test.js: -------------------------------------------------------------------------------- 1 | const EncryptionService = require('../services/EncryptionService'); 2 | const IPFSService = require('../services/IPFSService'); 3 | const ZKProofService = require('../services/ZKProofService'); 4 | const BlockchainService = require('../services/BlockchainService'); 5 | 6 | describe('Service Integration Tests', () => { 7 | let encryptionService; 8 | let ipfsService; 9 | let zkProofService; 10 | let blockchainService; 11 | 12 | beforeAll(() => { 13 | encryptionService = new EncryptionService(); 14 | ipfsService = new IPFSService(); 15 | zkProofService = new ZKProofService(); 16 | blockchainService = new BlockchainService( 17 | 'https://goerli.infura.io/v3/test', 18 | '0x1234567890123456789012345678901234567890123456789012345678901234' 19 | ); 20 | }); 21 | 22 | describe('Data Encryption and Storage Flow', () => { 23 | test('Should complete full data encryption and storage workflow', async () => { 24 | const originalData = 'Sensitive data that needs protection'; 25 | const encryptionKey = encryptionService.generateKey(); 26 | 27 | // Step 1: Encrypt data 28 | const encryptedData = encryptionService.encrypt(originalData, encryptionKey); 29 | expect(encryptedData).toHaveProperty('encrypted'); 30 | expect(encryptedData).toHaveProperty('iv'); 31 | expect(encryptedData).toHaveProperty('tag'); 32 | 33 | // Step 2: Generate data hash 34 | const dataHash = encryptionService.generateHash(originalData); 35 | expect(dataHash).toMatch(/^[a-f0-9]{64}$/); 36 | 37 | // Step 3: Generate HMAC for integrity 38 | const hmac = encryptionService.generateHMAC(originalData, encryptionKey); 39 | expect(hmac).toMatch(/^[a-f0-9]{64}$/); 40 | 41 | // Step 4: Verify HMAC 42 | const isValidHMAC = encryptionService.verifyHMAC(originalData, encryptionKey, hmac); 43 | expect(isValidHMAC).toBe(true); 44 | 45 | // Step 5: Decrypt data 46 | const decryptedData = encryptionService.decrypt(encryptedData, encryptionKey); 47 | expect(decryptedData).toBe(originalData); 48 | }); 49 | 50 | test('Should handle large data encryption', () => { 51 | const largeData = 'x'.repeat(10000); // 10KB of data 52 | const encryptionKey = encryptionService.generateKey(); 53 | 54 | const encryptedData = encryptionService.encrypt(largeData, encryptionKey); 55 | const decryptedData = encryptionService.decrypt(encryptedData, encryptionKey); 56 | 57 | expect(decryptedData).toBe(largeData); 58 | }); 59 | 60 | test('Should handle binary data encryption', () => { 61 | const binaryData = Buffer.from([0x00, 0x01, 0x02, 0x03, 0xFF, 0xFE, 0xFD]); 62 | const encryptionKey = encryptionService.generateKey(); 63 | 64 | const encryptedData = encryptionService.encrypt(binaryData, encryptionKey); 65 | const decryptedData = encryptionService.decrypt(encryptedData, encryptionKey); 66 | 67 | expect(Buffer.from(decryptedData)).toEqual(binaryData); 68 | }); 69 | }); 70 | 71 | describe('Zero Knowledge Proof Integration', () => { 72 | test('Should generate data verification proof with proper inputs', async () => { 73 | const dataHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'; 74 | const ownerAddress = '0x1234567890123456789012345678901234567890'; 75 | const conditions = { 76 | accessPolicy: 'private', 77 | price: '1000000000000000000', 78 | timestamp: Math.floor(Date.now() / 1000) 79 | }; 80 | 81 | try { 82 | const proof = await zkProofService.createDataVerificationProof( 83 | dataHash, 84 | ownerAddress, 85 | conditions 86 | ); 87 | 88 | expect(proof).toHaveProperty('circuitName', 'dataVerification'); 89 | expect(proof).toHaveProperty('timestamp'); 90 | } catch (error) { 91 | // Expected to fail without circuit files 92 | expect(error.message).toContain('Circuit files not found'); 93 | } 94 | }); 95 | 96 | test('Should generate access control proof', async () => { 97 | const requesterAddress = '0xabcdef1234567890abcdef1234567890abcdef12'; 98 | const dataHash = '0x1234567890abcdef1234567890abcdef12345678'; 99 | const permissions = { 100 | read: true, 101 | write: false, 102 | share: true, 103 | timestamp: Math.floor(Date.now() / 1000) 104 | }; 105 | 106 | try { 107 | const proof = await zkProofService.createAccessControlProof( 108 | requesterAddress, 109 | dataHash, 110 | permissions 111 | ); 112 | 113 | expect(proof).toHaveProperty('circuitName', 'accessControl'); 114 | expect(proof).toHaveProperty('timestamp'); 115 | } catch (error) { 116 | // Expected to fail without circuit files 117 | expect(error.message).toContain('Circuit files not found'); 118 | } 119 | }); 120 | 121 | test('Should verify data integrity', async () => { 122 | const dataHash = '0x1234567890abcdef1234567890abcdef12345678'; 123 | const proof = { 124 | proof: { 125 | pi_a: ['0x123', '0x456'], 126 | pi_b: [['0x789', '0xabc'], ['0xdef', '0x111']], 127 | pi_c: ['0x222', '0x333'] 128 | }, 129 | publicSignals: [dataHash, '0x1234567890123456789012345678901234567890'] 130 | }; 131 | 132 | try { 133 | const isValid = await zkProofService.verifyDataIntegrity(proof, dataHash); 134 | expect(typeof isValid).toBe('boolean'); 135 | } catch (error) { 136 | // Expected to fail without circuit files 137 | expect(error.message).toContain('Circuit files not found'); 138 | } 139 | }); 140 | }); 141 | 142 | describe('Blockchain Service Integration', () => { 143 | test('Should initialize blockchain service', () => { 144 | expect(blockchainService).toBeDefined(); 145 | expect(blockchainService.provider).toBeDefined(); 146 | expect(blockchainService.wallet).toBeDefined(); 147 | }); 148 | 149 | test('Should get network information', async () => { 150 | try { 151 | const networkInfo = await blockchainService.getNetworkInfo(); 152 | expect(networkInfo).toHaveProperty('chainId'); 153 | expect(networkInfo).toHaveProperty('name'); 154 | expect(networkInfo).toHaveProperty('blockNumber'); 155 | expect(networkInfo).toHaveProperty('timestamp'); 156 | } catch (error) { 157 | // Expected to fail without valid RPC URL 158 | expect(error.message).toContain('Failed to get network info'); 159 | } 160 | }); 161 | 162 | test('Should get gas price', async () => { 163 | try { 164 | const gasPrice = await blockchainService.getGasPrice(); 165 | expect(typeof gasPrice).toBe('string'); 166 | expect(gasPrice).toMatch(/^\d+$/); 167 | } catch (error) { 168 | // Expected to fail without valid RPC URL 169 | expect(error.message).toContain('Failed to get gas price'); 170 | } 171 | }); 172 | 173 | test('Should estimate gas for transaction', async () => { 174 | const mockContract = { 175 | testMethod: { 176 | estimateGas: jest.fn().mockResolvedValue('21000') 177 | } 178 | }; 179 | 180 | try { 181 | const gasEstimate = await blockchainService.estimateGas( 182 | mockContract, 183 | 'testMethod', 184 | [], 185 | { value: '1000000000000000000' } 186 | ); 187 | expect(gasEstimate).toBe('21000'); 188 | } catch (error) { 189 | expect(error.message).toContain('Gas estimation failed'); 190 | } 191 | }); 192 | }); 193 | 194 | describe('Error Handling', () => { 195 | test('Should handle encryption errors gracefully', () => { 196 | const encryptionService = new EncryptionService(); 197 | 198 | expect(() => { 199 | encryptionService.encrypt(null, Buffer.alloc(32)); 200 | }).toThrow('Encryption failed'); 201 | }); 202 | 203 | test('Should handle decryption errors gracefully', () => { 204 | const encryptionService = new EncryptionService(); 205 | const invalidEncryptedData = { 206 | encrypted: 'invalid', 207 | iv: 'invalid', 208 | tag: 'invalid', 209 | algorithm: 'aes-256-gcm' 210 | }; 211 | 212 | expect(() => { 213 | encryptionService.decrypt(invalidEncryptedData, Buffer.alloc(32)); 214 | }).toThrow('Decryption failed'); 215 | }); 216 | 217 | test('Should handle invalid HMAC verification', () => { 218 | const encryptionService = new EncryptionService(); 219 | const data = 'test data'; 220 | const key = encryptionService.generateKey(); 221 | const invalidHMAC = 'invalid-hmac'; 222 | 223 | const isValid = encryptionService.verifyHMAC(data, key, invalidHMAC); 224 | expect(isValid).toBe(false); 225 | }); 226 | }); 227 | 228 | describe('Performance Tests', () => { 229 | test('Should encrypt data within reasonable time', () => { 230 | const encryptionService = new EncryptionService(); 231 | const data = 'Performance test data'; 232 | const key = encryptionService.generateKey(); 233 | 234 | const startTime = Date.now(); 235 | const encryptedData = encryptionService.encrypt(data, key); 236 | const encryptionTime = Date.now() - startTime; 237 | 238 | expect(encryptionTime).toBeLessThan(100); // Should complete within 100ms 239 | expect(encryptedData).toHaveProperty('encrypted'); 240 | }); 241 | 242 | test('Should decrypt data within reasonable time', () => { 243 | const encryptionService = new EncryptionService(); 244 | const data = 'Performance test data'; 245 | const key = encryptionService.generateKey(); 246 | const encryptedData = encryptionService.encrypt(data, key); 247 | 248 | const startTime = Date.now(); 249 | const decryptedData = encryptionService.decrypt(encryptedData, key); 250 | const decryptionTime = Date.now() - startTime; 251 | 252 | expect(decryptionTime).toBeLessThan(100); // Should complete within 100ms 253 | expect(decryptedData).toBe(data); 254 | }); 255 | 256 | test('Should generate hash within reasonable time', () => { 257 | const encryptionService = new EncryptionService(); 258 | const data = 'Performance test data for hashing'; 259 | 260 | const startTime = Date.now(); 261 | const hash = encryptionService.generateHash(data); 262 | const hashTime = Date.now() - startTime; 263 | 264 | expect(hashTime).toBeLessThan(50); // Should complete within 50ms 265 | expect(hash).toMatch(/^[a-f0-9]{64}$/); 266 | }); 267 | }); 268 | }); 269 | -------------------------------------------------------------------------------- /src/contracts/ZeroDataToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 8 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 9 | 10 | /** 11 | * @title ZeroDataToken 12 | * @dev ERC20 token for ZeroData ecosystem 13 | */ 14 | contract ZeroDataToken is ERC20, ERC20Burnable, Ownable { 15 | 16 | uint256 public constant INITIAL_SUPPLY = 1000000000 * 10**18; // 1 billion tokens 17 | uint256 public constant MAX_SUPPLY = 10000000000 * 10**18; // 10 billion tokens max 18 | 19 | // Token distribution 20 | uint256 public constant COMMUNITY_RESERVE = 4000000000 * 10**18; // 40% 21 | uint256 public constant TEAM_RESERVE = 1500000000 * 10**18; // 15% 22 | uint256 public constant ECOSYSTEM_RESERVE = 2000000000 * 10**18; // 20% 23 | uint256 public constant TREASURY_RESERVE = 1500000000 * 10**18; // 15% 24 | uint256 public constant LIQUIDITY_RESERVE = 1000000000 * 10**18; // 10% 25 | 26 | // Vesting schedules 27 | mapping(address => uint256) public vestingAmount; 28 | mapping(address => uint256) public vestingStart; 29 | mapping(address => uint256) public vestingDuration; 30 | mapping(address => uint256) public vestingClaimed; 31 | 32 | // Staking pools 33 | struct StakingPool { 34 | uint256 totalStaked; 35 | uint256 rewardRate; 36 | uint256 lastUpdateTime; 37 | uint256 rewardPerTokenStored; 38 | mapping(address => uint256) userStaked; 39 | mapping(address => uint256) userRewardPerTokenPaid; 40 | mapping(address => uint256) userRewards; 41 | } 42 | 43 | mapping(string => StakingPool) public stakingPools; 44 | 45 | // Events 46 | event TokensVested(address indexed user, uint256 amount, uint256 timestamp); 47 | event Staked(address indexed user, string pool, uint256 amount, uint256 timestamp); 48 | event Unstaked(address indexed user, string pool, uint256 amount, uint256 timestamp); 49 | event RewardsClaimed(address indexed user, string pool, uint256 amount, uint256 timestamp); 50 | 51 | constructor() ERC20("ZeroData Token", "ZDT") { 52 | _mint(msg.sender, INITIAL_SUPPLY); 53 | 54 | // Initialize staking pools 55 | _initializeStakingPools(); 56 | } 57 | 58 | /** 59 | * @dev Initialize staking pools 60 | */ 61 | function _initializeStakingPools() internal { 62 | // Data Provider Pool 63 | stakingPools["dataProvider"].rewardRate = 1000000000000000000; // 1 token per second 64 | stakingPools["dataProvider"].lastUpdateTime = block.timestamp; 65 | 66 | // Validator Pool 67 | stakingPools["validator"].rewardRate = 500000000000000000; // 0.5 tokens per second 68 | stakingPools["validator"].lastUpdateTime = block.timestamp; 69 | 70 | // Storage Pool 71 | stakingPools["storage"].rewardRate = 250000000000000000; // 0.25 tokens per second 72 | stakingPools["storage"].lastUpdateTime = block.timestamp; 73 | } 74 | 75 | /** 76 | * @dev Mint tokens (only owner) 77 | * @param to Address to mint tokens to 78 | * @param amount Amount to mint 79 | */ 80 | function mint(address to, uint256 amount) external onlyOwner { 81 | require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply"); 82 | _mint(to, amount); 83 | } 84 | 85 | /** 86 | * @dev Set up vesting for an address 87 | * @param user Address to set up vesting for 88 | * @param amount Total amount to vest 89 | * @param duration Vesting duration in seconds 90 | */ 91 | function setupVesting(address user, uint256 amount, uint256 duration) external onlyOwner { 92 | require(vestingAmount[user] == 0, "Vesting already set up"); 93 | require(amount > 0, "Invalid vesting amount"); 94 | require(duration > 0, "Invalid vesting duration"); 95 | 96 | vestingAmount[user] = amount; 97 | vestingStart[user] = block.timestamp; 98 | vestingDuration[user] = duration; 99 | vestingClaimed[user] = 0; 100 | } 101 | 102 | /** 103 | * @dev Claim vested tokens 104 | */ 105 | function claimVestedTokens() external { 106 | require(vestingAmount[msg.sender] > 0, "No vesting set up"); 107 | 108 | uint256 claimableAmount = getClaimableVestedAmount(msg.sender); 109 | require(claimableAmount > 0, "No tokens available to claim"); 110 | 111 | vestingClaimed[msg.sender] += claimableAmount; 112 | _mint(msg.sender, claimableAmount); 113 | 114 | emit TokensVested(msg.sender, claimableAmount, block.timestamp); 115 | } 116 | 117 | /** 118 | * @dev Get claimable vested amount for user 119 | * @param user User address 120 | * @return Claimable amount 121 | */ 122 | function getClaimableVestedAmount(address user) public view returns (uint256) { 123 | if (vestingAmount[user] == 0) return 0; 124 | 125 | uint256 elapsed = block.timestamp - vestingStart[user]; 126 | if (elapsed >= vestingDuration[user]) { 127 | return vestingAmount[user] - vestingClaimed[user]; 128 | } 129 | 130 | uint256 totalClaimable = (vestingAmount[user] * elapsed) / vestingDuration[user]; 131 | return totalClaimable - vestingClaimed[user]; 132 | } 133 | 134 | /** 135 | * @dev Stake tokens in a pool 136 | * @param pool Pool name 137 | * @param amount Amount to stake 138 | */ 139 | function stake(string memory pool, uint256 amount) external { 140 | require(amount > 0, "Amount must be greater than 0"); 141 | require(bytes(pool).length > 0, "Invalid pool name"); 142 | 143 | StakingPool storage poolData = stakingPools[pool]; 144 | require(poolData.rewardRate > 0, "Pool does not exist"); 145 | 146 | _updateReward(msg.sender, pool); 147 | 148 | poolData.userStaked[msg.sender] += amount; 149 | poolData.totalStaked += amount; 150 | 151 | _transfer(msg.sender, address(this), amount); 152 | 153 | emit Staked(msg.sender, pool, amount, block.timestamp); 154 | } 155 | 156 | /** 157 | * @dev Unstake tokens from a pool 158 | * @param pool Pool name 159 | * @param amount Amount to unstake 160 | */ 161 | function unstake(string memory pool, uint256 amount) external { 162 | require(amount > 0, "Amount must be greater than 0"); 163 | 164 | StakingPool storage poolData = stakingPools[pool]; 165 | require(poolData.userStaked[msg.sender] >= amount, "Insufficient staked amount"); 166 | 167 | _updateReward(msg.sender, pool); 168 | 169 | poolData.userStaked[msg.sender] -= amount; 170 | poolData.totalStaked -= amount; 171 | 172 | _transfer(address(this), msg.sender, amount); 173 | 174 | emit Unstaked(msg.sender, pool, amount, block.timestamp); 175 | } 176 | 177 | /** 178 | * @dev Claim staking rewards 179 | * @param pool Pool name 180 | */ 181 | function claimRewards(string memory pool) external { 182 | StakingPool storage poolData = stakingPools[pool]; 183 | require(poolData.userStaked[msg.sender] > 0, "No staked tokens"); 184 | 185 | _updateReward(msg.sender, pool); 186 | 187 | uint256 reward = poolData.userRewards[msg.sender]; 188 | require(reward > 0, "No rewards to claim"); 189 | 190 | poolData.userRewards[msg.sender] = 0; 191 | _mint(msg.sender, reward); 192 | 193 | emit RewardsClaimed(msg.sender, pool, reward, block.timestamp); 194 | } 195 | 196 | /** 197 | * @dev Update reward for user 198 | * @param user User address 199 | * @param pool Pool name 200 | */ 201 | function _updateReward(address user, string memory pool) internal { 202 | StakingPool storage poolData = stakingPools[pool]; 203 | 204 | poolData.rewardPerTokenStored = rewardPerToken(pool); 205 | poolData.lastUpdateTime = block.timestamp; 206 | 207 | poolData.userRewards[user] = earned(user, pool); 208 | poolData.userRewardPerTokenPaid[user] = poolData.rewardPerTokenStored; 209 | } 210 | 211 | /** 212 | * @dev Calculate reward per token 213 | * @param pool Pool name 214 | * @return Reward per token 215 | */ 216 | function rewardPerToken(string memory pool) public view returns (uint256) { 217 | StakingPool storage poolData = stakingPools[pool]; 218 | 219 | if (poolData.totalStaked == 0) { 220 | return poolData.rewardPerTokenStored; 221 | } 222 | 223 | return poolData.rewardPerTokenStored + 224 | ((block.timestamp - poolData.lastUpdateTime) * poolData.rewardRate * 1e18) / 225 | poolData.totalStaked; 226 | } 227 | 228 | /** 229 | * @dev Calculate earned rewards for user 230 | * @param user User address 231 | * @param pool Pool name 232 | * @return Earned rewards 233 | */ 234 | function earned(address user, string memory pool) public view returns (uint256) { 235 | StakingPool storage poolData = stakingPools[pool]; 236 | 237 | return (poolData.userStaked[user] * 238 | (rewardPerToken(pool) - poolData.userRewardPerTokenPaid[user])) / 239 | 1e18 + poolData.userRewards[user]; 240 | } 241 | 242 | /** 243 | * @dev Get user staking info 244 | * @param user User address 245 | * @param pool Pool name 246 | * @return Staked amount and earned rewards 247 | */ 248 | function getUserStakingInfo(address user, string memory pool) 249 | external 250 | view 251 | returns (uint256 staked, uint256 rewards) 252 | { 253 | StakingPool storage poolData = stakingPools[pool]; 254 | return (poolData.userStaked[user], earned(user, pool)); 255 | } 256 | 257 | /** 258 | * @dev Get pool info 259 | * @param pool Pool name 260 | * @return Total staked, reward rate, and reward per token 261 | */ 262 | function getPoolInfo(string memory pool) 263 | external 264 | view 265 | returns (uint256 totalStaked, uint256 rewardRate, uint256 rewardPerTokenStored) 266 | { 267 | StakingPool storage poolData = stakingPools[pool]; 268 | return ( 269 | poolData.totalStaked, 270 | poolData.rewardRate, 271 | rewardPerToken(pool) 272 | ); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/routes/did.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const DIDService = require('../services/DIDService'); 3 | 4 | const router = express.Router(); 5 | 6 | // Initialize DID service 7 | const didService = new DIDService(); 8 | 9 | /** 10 | * POST /api/did/create 11 | * Create a new DID 12 | */ 13 | router.post('/create', async (req, res) => { 14 | try { 15 | const { address } = req.body; 16 | 17 | if (!address || !address.match(/^0x[a-fA-F0-9]{40}$/)) { 18 | return res.status(400).json({ 19 | error: 'Valid Ethereum address is required' 20 | }); 21 | } 22 | 23 | const didResult = didService.generateDID(address); 24 | 25 | res.status(201).json({ 26 | message: 'DID created successfully', 27 | ...didResult 28 | }); 29 | } catch (error) { 30 | res.status(500).json({ 31 | error: 'DID creation failed', 32 | message: error.message 33 | }); 34 | } 35 | }); 36 | 37 | /** 38 | * GET /api/did/:did 39 | * Resolve a DID to its document 40 | */ 41 | router.get('/:did', async (req, res) => { 42 | try { 43 | const { did } = req.params; 44 | 45 | if (!did.startsWith('did:zerodata:')) { 46 | return res.status(400).json({ 47 | error: 'Invalid DID format' 48 | }); 49 | } 50 | 51 | const didDocument = await didService.resolveDID(did); 52 | 53 | res.json({ 54 | message: 'DID resolved successfully', 55 | didDocument: didDocument 56 | }); 57 | } catch (error) { 58 | res.status(500).json({ 59 | error: 'DID resolution failed', 60 | message: error.message 61 | }); 62 | } 63 | }); 64 | 65 | /** 66 | * POST /api/did/credential/create 67 | * Create a verifiable credential 68 | */ 69 | router.post('/credential/create', async (req, res) => { 70 | try { 71 | const { issuerDID, holderDID, credentialData, privateKey } = req.body; 72 | 73 | if (!issuerDID || !holderDID || !credentialData || !privateKey) { 74 | return res.status(400).json({ 75 | error: 'All fields are required: issuerDID, holderDID, credentialData, privateKey' 76 | }); 77 | } 78 | 79 | const credential = didService.createVerifiableCredential( 80 | issuerDID, 81 | holderDID, 82 | credentialData, 83 | privateKey 84 | ); 85 | 86 | res.status(201).json({ 87 | message: 'Verifiable credential created successfully', 88 | credential: credential 89 | }); 90 | } catch (error) { 91 | res.status(500).json({ 92 | error: 'Credential creation failed', 93 | message: error.message 94 | }); 95 | } 96 | }); 97 | 98 | /** 99 | * POST /api/did/credential/verify 100 | * Verify a verifiable credential 101 | */ 102 | router.post('/credential/verify', async (req, res) => { 103 | try { 104 | const { credential, issuerDID } = req.body; 105 | 106 | if (!credential || !issuerDID) { 107 | return res.status(400).json({ 108 | error: 'Credential and issuerDID are required' 109 | }); 110 | } 111 | 112 | const isValid = didService.verifyVerifiableCredential(credential, issuerDID); 113 | 114 | res.json({ 115 | message: 'Credential verification completed', 116 | valid: isValid, 117 | verifiedAt: new Date().toISOString() 118 | }); 119 | } catch (error) { 120 | res.status(500).json({ 121 | error: 'Credential verification failed', 122 | message: error.message 123 | }); 124 | } 125 | }); 126 | 127 | /** 128 | * POST /api/did/presentation/create 129 | * Create a verifiable presentation 130 | */ 131 | router.post('/presentation/create', async (req, res) => { 132 | try { 133 | const { holderDID, credentials, privateKey, challenge } = req.body; 134 | 135 | if (!holderDID || !credentials || !privateKey) { 136 | return res.status(400).json({ 137 | error: 'holderDID, credentials, and privateKey are required' 138 | }); 139 | } 140 | 141 | const presentation = didService.createVerifiablePresentation( 142 | holderDID, 143 | credentials, 144 | privateKey, 145 | challenge 146 | ); 147 | 148 | res.status(201).json({ 149 | message: 'Verifiable presentation created successfully', 150 | presentation: presentation 151 | }); 152 | } catch (error) { 153 | res.status(500).json({ 154 | error: 'Presentation creation failed', 155 | message: error.message 156 | }); 157 | } 158 | }); 159 | 160 | /** 161 | * POST /api/did/presentation/verify 162 | * Verify a verifiable presentation 163 | */ 164 | router.post('/presentation/verify', async (req, res) => { 165 | try { 166 | const { presentation, holderDID } = req.body; 167 | 168 | if (!presentation || !holderDID) { 169 | return res.status(400).json({ 170 | error: 'Presentation and holderDID are required' 171 | }); 172 | } 173 | 174 | const isValid = didService.verifyVerifiablePresentation(presentation, holderDID); 175 | 176 | res.json({ 177 | message: 'Presentation verification completed', 178 | valid: isValid, 179 | verifiedAt: new Date().toISOString() 180 | }); 181 | } catch (error) { 182 | res.status(500).json({ 183 | error: 'Presentation verification failed', 184 | message: error.message 185 | }); 186 | } 187 | }); 188 | 189 | /** 190 | * POST /api/did/auth/challenge 191 | * Create authentication challenge 192 | */ 193 | router.post('/auth/challenge', async (req, res) => { 194 | try { 195 | const { did } = req.body; 196 | 197 | if (!did || !did.startsWith('did:zerodata:')) { 198 | return res.status(400).json({ 199 | error: 'Valid DID is required' 200 | }); 201 | } 202 | 203 | const challenge = didService.createAuthChallenge(did); 204 | 205 | res.status(201).json({ 206 | message: 'Authentication challenge created', 207 | challenge: challenge 208 | }); 209 | } catch (error) { 210 | res.status(500).json({ 211 | error: 'Challenge creation failed', 212 | message: error.message 213 | }); 214 | } 215 | }); 216 | 217 | /** 218 | * POST /api/did/auth/verify 219 | * Verify authentication response 220 | */ 221 | router.post('/auth/verify', async (req, res) => { 222 | try { 223 | const { challenge, response } = req.body; 224 | 225 | if (!challenge || !response) { 226 | return res.status(400).json({ 227 | error: 'Challenge and response are required' 228 | }); 229 | } 230 | 231 | const isValid = didService.verifyAuthResponse(challenge, response); 232 | 233 | res.json({ 234 | message: 'Authentication verification completed', 235 | authenticated: isValid, 236 | verifiedAt: new Date().toISOString() 237 | }); 238 | } catch (error) { 239 | res.status(500).json({ 240 | error: 'Authentication verification failed', 241 | message: error.message 242 | }); 243 | } 244 | }); 245 | 246 | /** 247 | * GET /api/did/user/:address/credentials 248 | * Get user's credentials 249 | */ 250 | router.get('/user/:address/credentials', async (req, res) => { 251 | try { 252 | const { address } = req.params; 253 | 254 | if (!address || !address.match(/^0x[a-fA-F0-9]{40}$/)) { 255 | return res.status(400).json({ 256 | error: 'Valid Ethereum address is required' 257 | }); 258 | } 259 | 260 | // TODO: Get real credentials from database 261 | const credentials = [ 262 | { 263 | id: `did:zerodata:${address}#credential-1`, 264 | type: ['VerifiableCredential', 'DataProviderCredential'], 265 | issuer: 'did:zerodata:0x1234567890123456789012345678901234567890', 266 | issuanceDate: new Date().toISOString(), 267 | credentialSubject: { 268 | id: `did:zerodata:${address}`, 269 | dataProviderLevel: 'verified', 270 | reputationScore: 95, 271 | totalDataProvided: 150 272 | } 273 | }, 274 | { 275 | id: `did:zerodata:${address}#credential-2`, 276 | type: ['VerifiableCredential', 'ValidatorCredential'], 277 | issuer: 'did:zerodata:0xabcdef1234567890abcdef1234567890abcdef12', 278 | issuanceDate: new Date().toISOString(), 279 | credentialSubject: { 280 | id: `did:zerodata:${address}`, 281 | validatorLevel: 'expert', 282 | accuracyRate: 99.2, 283 | totalValidations: 5000 284 | } 285 | } 286 | ]; 287 | 288 | res.json({ 289 | address: address, 290 | credentials: credentials, 291 | total: credentials.length 292 | }); 293 | } catch (error) { 294 | res.status(500).json({ 295 | error: 'Failed to get user credentials', 296 | message: error.message 297 | }); 298 | } 299 | }); 300 | 301 | /** 302 | * GET /api/did/user/:address/presentations 303 | * Get user's presentations 304 | */ 305 | router.get('/user/:address/presentations', async (req, res) => { 306 | try { 307 | const { address } = req.params; 308 | 309 | if (!address || !address.match(/^0x[a-fA-F0-9]{40}$/)) { 310 | return res.status(400).json({ 311 | error: 'Valid Ethereum address is required' 312 | }); 313 | } 314 | 315 | // TODO: Get real presentations from database 316 | const presentations = [ 317 | { 318 | id: `did:zerodata:${address}#presentation-1`, 319 | type: ['VerifiablePresentation', 'DataAccessPresentation'], 320 | holder: `did:zerodata:${address}`, 321 | created: new Date().toISOString(), 322 | verifiableCredential: [ 323 | { 324 | id: `did:zerodata:${address}#credential-1`, 325 | type: ['VerifiableCredential', 'DataProviderCredential'] 326 | } 327 | ] 328 | } 329 | ]; 330 | 331 | res.json({ 332 | address: address, 333 | presentations: presentations, 334 | total: presentations.length 335 | }); 336 | } catch (error) { 337 | res.status(500).json({ 338 | error: 'Failed to get user presentations', 339 | message: error.message 340 | }); 341 | } 342 | }); 343 | 344 | /** 345 | * GET /api/did/stats 346 | * Get DID system statistics 347 | */ 348 | router.get('/stats', async (req, res) => { 349 | try { 350 | // TODO: Get real statistics from database 351 | const stats = { 352 | totalDIDs: 2500, 353 | activeDIDs: 2100, 354 | totalCredentials: 15000, 355 | totalPresentations: 8500, 356 | verifiedProviders: 450, 357 | verifiedValidators: 200, 358 | verifiedStorageNodes: 150, 359 | lastUpdated: new Date().toISOString() 360 | }; 361 | 362 | res.json({ 363 | message: 'DID statistics retrieved successfully', 364 | ...stats 365 | }); 366 | } catch (error) { 367 | res.status(500).json({ 368 | error: 'Failed to get DID statistics', 369 | message: error.message 370 | }); 371 | } 372 | }); 373 | 374 | module.exports = router; 375 | -------------------------------------------------------------------------------- /src/tests/integration.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const app = require('../src/index'); 3 | 4 | describe('ZeroData Integration Tests', () => { 5 | let authToken; 6 | let testUser; 7 | let testDataHash; 8 | 9 | beforeAll(async () => { 10 | // Setup test user 11 | testUser = { 12 | address: '0x1234567890123456789012345678901234567890', 13 | signature: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12', 14 | message: 'ZeroData Authentication\nAddress: 0x1234567890123456789012345678901234567890\nTimestamp: 1692000000' 15 | }; 16 | }); 17 | 18 | describe('Complete Data Lifecycle', () => { 19 | test('Should complete full data lifecycle: register -> list -> purchase -> verify', async () => { 20 | // Step 1: Register user 21 | const registerResponse = await request(app) 22 | .post('/api/auth/register') 23 | .send(testUser) 24 | .expect(201); 25 | 26 | authToken = registerResponse.body.token; 27 | 28 | // Step 2: Upload data 29 | const uploadResponse = await request(app) 30 | .post('/api/data/upload') 31 | .set('Authorization', `Bearer ${authToken}`) 32 | .send({ 33 | data: 'Integration test data for ZeroData', 34 | accessPolicy: 'private', 35 | price: '1000000000000000000', 36 | description: 'Test data for integration testing' 37 | }) 38 | .expect(201); 39 | 40 | testDataHash = uploadResponse.body.dataHash; 41 | 42 | // Step 3: List data in marketplace 43 | const listResponse = await request(app) 44 | .post('/api/market/list') 45 | .set('Authorization', `Bearer ${authToken}`) 46 | .send({ 47 | dataHash: testDataHash, 48 | price: '1000000000000000000', 49 | description: 'Test data for marketplace', 50 | category: 'general', 51 | tags: ['test', 'integration'] 52 | }) 53 | .expect(201); 54 | 55 | // Step 4: Browse marketplace 56 | const browseResponse = await request(app) 57 | .get('/api/market/browse') 58 | .expect(200); 59 | 60 | expect(browseResponse.body.data).toBeDefined(); 61 | expect(Array.isArray(browseResponse.body.data)).toBe(true); 62 | 63 | // Step 5: Request data download 64 | const downloadResponse = await request(app) 65 | .post(`/api/data/${testDataHash}/download`) 66 | .set('Authorization', `Bearer ${authToken}`) 67 | .send({ 68 | requesterAddress: testUser.address, 69 | permissions: { read: true, write: false } 70 | }) 71 | .expect(200); 72 | 73 | expect(downloadResponse.body.status).toBe('access_granted'); 74 | 75 | // Step 6: Verify data integrity 76 | const verifyResponse = await request(app) 77 | .post(`/api/data/${testDataHash}/verify`) 78 | .set('Authorization', `Bearer ${authToken}`) 79 | .send({ 80 | proof: { 81 | proof: { 82 | pi_a: ['0x123', '0x456'], 83 | pi_b: [['0x789', '0xabc'], ['0xdef', '0x111']], 84 | pi_c: ['0x222', '0x333'] 85 | }, 86 | publicSignals: [testDataHash, testUser.address] 87 | } 88 | }) 89 | .expect(200); 90 | 91 | expect(verifyResponse.body.verified).toBeDefined(); 92 | }); 93 | }); 94 | 95 | describe('DID Integration', () => { 96 | test('Should create DID and verifiable credentials', async () => { 97 | // Step 1: Create DID 98 | const didResponse = await request(app) 99 | .post('/api/did/create') 100 | .send({ address: testUser.address }) 101 | .expect(201); 102 | 103 | expect(didResponse.body.did).toBeDefined(); 104 | expect(didResponse.body.didDocument).toBeDefined(); 105 | 106 | // Step 2: Create verifiable credential 107 | const credentialResponse = await request(app) 108 | .post('/api/did/credential/create') 109 | .send({ 110 | issuerDID: didResponse.body.did, 111 | holderDID: didResponse.body.did, 112 | credentialData: { 113 | dataProviderLevel: 'verified', 114 | reputationScore: 95 115 | }, 116 | privateKey: didResponse.body.privateKey 117 | }) 118 | .expect(201); 119 | 120 | expect(credentialResponse.body.credential).toBeDefined(); 121 | 122 | // Step 3: Verify credential 123 | const verifyCredentialResponse = await request(app) 124 | .post('/api/did/credential/verify') 125 | .send({ 126 | credential: credentialResponse.body.credential, 127 | issuerDID: didResponse.body.did 128 | }) 129 | .expect(200); 130 | 131 | expect(verifyCredentialResponse.body.valid).toBeDefined(); 132 | }); 133 | }); 134 | 135 | describe('Multi-chain Integration', () => { 136 | test('Should interact with multiple blockchain networks', async () => { 137 | // Step 1: Get supported networks 138 | const networksResponse = await request(app) 139 | .get('/api/chains/networks') 140 | .expect(200); 141 | 142 | expect(networksResponse.body.networks).toBeDefined(); 143 | expect(Array.isArray(networksResponse.body.networks)).toBe(true); 144 | 145 | // Step 2: Get network information 146 | const networkInfoResponse = await request(app) 147 | .get('/api/chains/networks/ethereum') 148 | .expect(200); 149 | 150 | expect(networkInfoResponse.body.network).toBeDefined(); 151 | expect(networkInfoResponse.body.network.name).toBe('Ethereum'); 152 | 153 | // Step 3: Get cross-chain status 154 | const statusResponse = await request(app) 155 | .get(`/api/chains/status/${testDataHash}`) 156 | .expect(200); 157 | 158 | expect(statusResponse.body.networks).toBeDefined(); 159 | }); 160 | }); 161 | 162 | describe('DAO Governance Integration', () => { 163 | test('Should participate in DAO governance', async () => { 164 | // Step 1: Get governance parameters 165 | const paramsResponse = await request(app) 166 | .get('/api/dao/governance-parameters') 167 | .expect(200); 168 | 169 | expect(paramsResponse.body.votingDelay).toBeDefined(); 170 | expect(paramsResponse.body.votingPeriod).toBeDefined(); 171 | 172 | // Step 2: Get proposals 173 | const proposalsResponse = await request(app) 174 | .get('/api/dao/proposals') 175 | .expect(200); 176 | 177 | expect(proposalsResponse.body.proposals).toBeDefined(); 178 | expect(Array.isArray(proposalsResponse.body.proposals)).toBe(true); 179 | 180 | // Step 3: Get DAO statistics 181 | const statsResponse = await request(app) 182 | .get('/api/dao/stats') 183 | .expect(200); 184 | 185 | expect(statsResponse.body.totalProposals).toBeDefined(); 186 | }); 187 | }); 188 | 189 | describe('Incentive System Integration', () => { 190 | test('Should interact with incentive system', async () => { 191 | // Step 1: Get staking pools 192 | const poolsResponse = await request(app) 193 | .get('/api/incentives/pools') 194 | .expect(200); 195 | 196 | expect(poolsResponse.body.pools).toBeDefined(); 197 | expect(Array.isArray(poolsResponse.body.pools)).toBe(true); 198 | 199 | // Step 2: Get user staking info 200 | const stakingResponse = await request(app) 201 | .get(`/api/incentives/user/${testUser.address}/staking`) 202 | .expect(200); 203 | 204 | expect(stakingResponse.body.address).toBe(testUser.address); 205 | expect(stakingResponse.body.staking).toBeDefined(); 206 | 207 | // Step 3: Get incentive statistics 208 | const statsResponse = await request(app) 209 | .get('/api/incentives/stats') 210 | .expect(200); 211 | 212 | expect(statsResponse.body.totalStaked).toBeDefined(); 213 | }); 214 | }); 215 | 216 | describe('Error Handling and Edge Cases', () => { 217 | test('Should handle invalid authentication gracefully', async () => { 218 | const response = await request(app) 219 | .get('/api/data/user/test') 220 | .expect(401); 221 | 222 | expect(response.body.error).toBe('No token provided'); 223 | }); 224 | 225 | test('Should handle invalid data hash gracefully', async () => { 226 | const response = await request(app) 227 | .get('/api/data/invalid-hash') 228 | .expect(200); // Returns mock data 229 | 230 | expect(response.body.dataHash).toBe('invalid-hash'); 231 | }); 232 | 233 | test('Should handle network errors gracefully', async () => { 234 | const response = await request(app) 235 | .get('/api/chains/networks/invalid-network') 236 | .expect(404); 237 | 238 | expect(response.body.error).toBe('Network not found'); 239 | }); 240 | }); 241 | 242 | describe('Performance Tests', () => { 243 | test('Should handle multiple concurrent requests', async () => { 244 | const requests = Array.from({ length: 10 }, () => 245 | request(app) 246 | .get('/health') 247 | .expect(200) 248 | ); 249 | 250 | const responses = await Promise.all(requests); 251 | 252 | responses.forEach(response => { 253 | expect(response.body.status).toBe('healthy'); 254 | }); 255 | }); 256 | 257 | test('Should respond to API calls within reasonable time', async () => { 258 | const startTime = Date.now(); 259 | 260 | await request(app) 261 | .get('/api/market/categories') 262 | .expect(200); 263 | 264 | const responseTime = Date.now() - startTime; 265 | expect(responseTime).toBeLessThan(1000); // Should respond within 1 second 266 | }); 267 | }); 268 | 269 | describe('Data Consistency Tests', () => { 270 | test('Should maintain data consistency across operations', async () => { 271 | // Upload data 272 | const uploadResponse = await request(app) 273 | .post('/api/data/upload') 274 | .set('Authorization', `Bearer ${authToken}`) 275 | .send({ 276 | data: 'Consistency test data', 277 | accessPolicy: 'public', 278 | price: '0', 279 | description: 'Test for data consistency' 280 | }) 281 | .expect(201); 282 | 283 | const dataHash = uploadResponse.body.dataHash; 284 | 285 | // Get data metadata 286 | const metadataResponse = await request(app) 287 | .get(`/api/data/${dataHash}`) 288 | .expect(200); 289 | 290 | expect(metadataResponse.body.dataHash).toBe(dataHash); 291 | 292 | // Get user data 293 | const userDataResponse = await request(app) 294 | .get(`/api/data/user/${testUser.address}`) 295 | .set('Authorization', `Bearer ${authToken}`) 296 | .expect(200); 297 | 298 | expect(userDataResponse.body.address).toBe(testUser.address); 299 | expect(Array.isArray(userDataResponse.body.data)).toBe(true); 300 | }); 301 | }); 302 | }); 303 | --------------------------------------------------------------------------------