UNPKG

linea-mcp

Version:

A Model Context Protocol server for interacting with the Linea blockchain

150 lines (149 loc) 6.96 kB
import { generatePrivateKey, privateKeyToAccount, mnemonicToAccount, } from 'viem/accounts'; import crypto from 'crypto'; import config from '../config/index.js'; /** * Service for managing cryptographic keys and accounts using viem */ class KeyManagementService { encryptionKey; defaultPrivateKey; /** * Create a new KeyManagementService instance */ constructor() { this.encryptionKey = config.security.privateKeyEncryptionKey || ''; this.defaultPrivateKey = process.env.WALLET_PRIVATE_KEY; // For demonstration purposes, if no key is provided, generate a random one if (!this.encryptionKey) { console.warn('No encryption key provided, using a random one for demonstration'); this.encryptionKey = crypto.randomBytes(32).toString('hex'); } } /** * Generate a new random private key account * @returns A new PrivateKeyAccount instance */ generateAccount() { const privateKey = generatePrivateKey(); return privateKeyToAccount(privateKey); } /** * Get the default account from environment variable or config * @returns The default account (PrivateKeyAccount or HDAccount) or a random one if not configured */ getDefaultAccount() { // Check for private key or mnemonic from env var first, then from config const privateKeyOrMnemonic = this.defaultPrivateKey || config.wallet?.privateKey; console.log('Checking for wallet private key or mnemonic...'); console.log('Private key/mnemonic from env exists:', !!this.defaultPrivateKey); console.log('Private key/mnemonic from config exists:', !!config.wallet?.privateKey); if (privateKeyOrMnemonic) { try { // Check if it's a mnemonic phrase (has spaces, likely 12 or 24 words) if (privateKeyOrMnemonic.includes(' ')) { console.log('Detected mnemonic phrase, creating account from mnemonic'); try { // Create account from mnemonic (viem syntax) // Default path is m/44'/60'/0'/0/0 which matches ethers default const account = mnemonicToAccount(privateKeyOrMnemonic); console.log('Successfully created account from mnemonic with address:', account.address); return account; } catch (mnemonicError) { console.error('Error creating account from mnemonic:', mnemonicError); throw new Error('Invalid mnemonic phrase provided'); } } // Otherwise, treat as a regular private key (hex string) // Ensure it starts with 0x for viem const formattedPrivateKey = privateKeyOrMnemonic.startsWith('0x') ? privateKeyOrMnemonic : `0x${privateKeyOrMnemonic}`; console.log('Treating input as raw private key'); const account = privateKeyToAccount(formattedPrivateKey); console.log('Successfully created account from private key with address:', account.address); return account; } catch (error) { console.error('Error creating account:', error); console.warn('Invalid private key or mnemonic in environment variables, using a random account'); const randomAccount = this.generateAccount(); console.log('Created random account with address:', randomAccount.address); return randomAccount; } } console.warn('No private key or mnemonic provided in environment variables, using a random account'); const randomAccount = this.generateAccount(); console.log('Created random account with address:', randomAccount.address); return randomAccount; } /** * Create an account from a private key * @param privateKey The private key (hex string, optionally starting with 0x) * @returns A PrivateKeyAccount instance */ createAccountFromPrivateKey(privateKey) { // Ensure it starts with 0x for viem const formattedPrivateKey = privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`; return privateKeyToAccount(formattedPrivateKey); } /** * Encrypt a private key for secure storage * @param privateKey The private key to encrypt (should be hex string) * @returns The encrypted private key string (iv:encryptedKey) */ encryptPrivateKey(privateKey) { // Ensure input is hex before encrypting const keyToEncrypt = privateKey.startsWith('0x') ? privateKey.substring(2) : privateKey; const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-ctr', Buffer.from(this.encryptionKey.padEnd(32).slice(0, 32)), // Ensure key is 32 bytes iv); const encryptedKey = Buffer.concat([ cipher.update(keyToEncrypt, 'hex'), // Assuming private key is hex cipher.final(), ]); return `${iv.toString('hex')}:${encryptedKey.toString('hex')}`; } /** * Decrypt a stored private key * @param encryptedKey The encrypted private key string (iv:encryptedKey) * @returns The decrypted private key (hex string, without 0x prefix) */ decryptPrivateKey(encryptedKey) { const parts = encryptedKey.split(':'); if (parts.length !== 2) { throw new Error("Invalid encrypted key format. Expected 'iv:encryptedKey'."); } const [ivHex, encryptedKeyHex] = parts; const iv = Buffer.from(ivHex, 'hex'); const encryptedKeyBuffer = Buffer.from(encryptedKeyHex, 'hex'); const decipher = crypto.createDecipheriv('aes-256-ctr', Buffer.from(this.encryptionKey.padEnd(32).slice(0, 32)), // Ensure key is 32 bytes iv); const decryptedKey = Buffer.concat([ decipher.update(encryptedKeyBuffer), decipher.final(), ]); // Return as hex string, which is what viem expects return decryptedKey.toString('hex'); } /** * Create an account from an encrypted private key * @param encryptedKey The encrypted private key string * @returns A PrivateKeyAccount instance */ createAccountFromEncryptedKey(encryptedKey) { const privateKey = this.decryptPrivateKey(encryptedKey); // Add the '0x' prefix back for viem return this.createAccountFromPrivateKey(`0x${privateKey}`); } /** * Generate a secure encryption key * @returns A new random encryption key (hex string) */ static generateEncryptionKey() { return crypto.randomBytes(32).toString('hex'); } } export default KeyManagementService;