const nacl = require('tweetnacl'); const crypto = require('crypto'); class ClipsCrypto { constructor() { this.addressPrefix = 'CLIP-'; } /** * Generate a new Ed25519 key pair * @returns {Object} Contains publicKey, privateKey, and address */ generateKeyPair() { const keyPair = nacl.sign.keyPair(); return { publicKey: Buffer.from(keyPair.publicKey).toString('hex'), privateKey: Buffer.from(keyPair.secretKey).toString('hex'), address: this.publicKeyToAddress(keyPair.publicKey) }; } /** * Convert public key to CLIPS address format * @param {Uint8Array|Buffer|string} publicKey - The public key * @returns {string} CLIPS address */ publicKeyToAddress(publicKey) { let pubKeyBuffer; if (typeof publicKey === 'string') { pubKeyBuffer = Buffer.from(publicKey, 'hex'); } else if (publicKey instanceof Uint8Array) { pubKeyBuffer = Buffer.from(publicKey); } else { pubKeyBuffer = publicKey; } return this.addressPrefix + pubKeyBuffer.toString('hex').toUpperCase(); } /** * Validate CLIPS address format * @param {string} address - The address to validate * @returns {boolean} True if valid */ isValidAddress(address) { if (!address || typeof address !== 'string') { return false; } // Check prefix if (!address.startsWith(this.addressPrefix)) { return false; } // Check hex part (should be 64 characters after prefix) const hexPart = address.slice(this.addressPrefix.length); if (hexPart.length !== 64) { return false; } // Check if hex is valid return /^[0-9A-Fa-f]+$/.test(hexPart); } /** * Sign a transaction * @param {string} privateKeyHex - The private key in hex format * @param {Object} transaction - Transaction data * @returns {string} Signature in hex format */ signTransaction(privateKeyHex, transaction) { const privateKey = Buffer.from(privateKeyHex, 'hex'); // Create transaction message to sign const txMessage = this.createTransactionMessage(transaction); const messageBuffer = Buffer.from(txMessage, 'utf8'); // Sign the message const signature = nacl.sign.detached(messageBuffer, privateKey); return Buffer.from(signature).toString('hex'); } /** * Create transaction message for signing * @param {Object} tx - Transaction object * @returns {string} Message to sign */ createTransactionMessage(tx) { // Format: sender:receiver:amount:nonce:type:data const parts = [ tx.sender || '', tx.receiver || '', tx.amount || '0', tx.nonce || '0', tx.type || 'transfer', tx.data || '' ]; return parts.join(':'); } /** * Create a complete transaction object * @param {Object} params - Transaction parameters * @returns {Object} Complete transaction object */ createTransaction(params) { const { sender, receiver, amount, nonce, type = 'transfer', data = '', privateKey } = params; const transaction = { sender, receiver, amount: parseInt(amount), nonce: parseInt(nonce), type, data, timestamp: Date.now() }; // Sign the transaction if (privateKey) { transaction.signature = this.signTransaction(privateKey, transaction); } return transaction; } /** * Verify a transaction signature * @param {Object} transaction - The transaction object * @param {string} publicKeyHex - The public key to verify against * @returns {boolean} True if signature is valid */ verifyTransaction(transaction, publicKeyHex) { if (!transaction.signature) { return false; } try { const publicKey = Buffer.from(publicKeyHex, 'hex'); const signature = Buffer.from(transaction.signature, 'hex'); // Recreate the message that was signed const txMessage = this.createTransactionMessage(transaction); const messageBuffer = Buffer.from(txMessage, 'utf8'); return nacl.sign.detached.verify(messageBuffer, signature, publicKey); } catch (error) { console.error('Error verifying transaction:', error); return false; } } /** * Convert transaction to hex string for broadcasting * @param {Object} transaction - The transaction object * @returns {string} Transaction as hex string */ transactionToHex(transaction) { const txString = JSON.stringify(transaction); return Buffer.from(txString, 'utf8').toString('hex'); } /** * Convert hex string back to transaction object * @param {string} txHex - Transaction in hex format * @returns {Object} Transaction object */ hexToTransaction(txHex) { const txString = Buffer.from(txHex, 'hex').toString('utf8'); return JSON.parse(txString); } /** * Import private key and return key pair info * @param {string} privateKeyHex - Private key in hex format * @returns {Object} Key pair information */ importPrivateKey(privateKeyHex) { try { const privateKey = Buffer.from(privateKeyHex, 'hex'); // Generate public key from private key const keyPair = nacl.sign.keyPair.fromSecretKey(privateKey); return { publicKey: Buffer.from(keyPair.publicKey).toString('hex'), privateKey: privateKeyHex, address: this.publicKeyToAddress(keyPair.publicKey) }; } catch (error) { throw new Error('Invalid private key format'); } } /** * Generate a random seed phrase (12 words) * @returns {string} Seed phrase */ generateSeedPhrase() { // Simple word list for demo purposes const wordList = [ 'abandon', 'ability', 'able', 'about', 'above', 'absent', 'absorb', 'abstract', 'absurd', 'abuse', 'access', 'accident', 'account', 'accuse', 'achieve', 'acid', 'acoustic', 'acquire', 'across', 'act', 'action', 'actor', 'actress', 'actual', 'adapt', 'add', 'addict', 'address', 'adjust', 'admit', 'adult', 'advance', 'advice', 'aerobic', 'affair', 'afford', 'afraid', 'again', 'against', 'agent', 'agree', 'ahead', 'aim', 'air', 'airport', 'aisle', 'alarm', 'album', 'alcohol', 'alert', 'alien', 'all', 'alley', 'allow', 'almost', 'alone', 'alpha', 'already', 'also', 'alter', 'always', 'amateur', 'amazing', 'among', 'amount', 'amused', 'analyst', 'anchor', 'ancient', 'anger', 'angle', 'angry', 'animal', 'ankle', 'announce', 'annual', 'another', 'answer', 'antenna', 'antique' ]; const words = []; for (let i = 0; i < 12; i++) { const randomIndex = crypto.randomInt(0, wordList.length); words.push(wordList[randomIndex]); } return words.join(' '); } } module.exports = new ClipsCrypto();