const crypto = require('crypto') const { signTypedData } = require('@metamask/eth-sig-util') const { TransactionFactory } = require('@ethereumjs/tx') const { Common } = require('@ethereumjs/common') const { hashPersonalMessage, toBuffer, ecsign, addHexPrefix, pubToAddress, ecrecover } = require('@ethereumjs/util') function chainConfig(chain, hardfork) { const chainId = BigInt(chain) return Common.isSupportedChainId(chainId) ? new Common({ chain: chainId, hardfork }) : Common.custom({ chainId: chainId }, { baseChain: 'mainnet', hardfork }) } class HotSignerWorker { constructor() { this.token = crypto.randomBytes(32).toString('hex') //process.send({ type: 'token', token: this.token }) } handleMessage({ id, method, params, token }) { // Define (pseudo) callback const pseudoCallback = (error, result) => { // Add correlation id to response const response = { id, error, result, type: 'rpc' } // Send response to parent process process.send(response) } // Verify token if (!crypto.timingSafeEqual(Buffer.from(token), Buffer.from(this.token))) return pseudoCallback('Invalid token') // If method exists -> execute if (this[method]) return this[method](params, pseudoCallback) // Else return error pseudoCallback(`Invalid method: '${method}'`) } signMessage(key, message, pseudoCallback) { // Hash message const hash = hashPersonalMessage(toBuffer(message)) // Sign message const signed = ecsign(hash, key) // Return serialized signed message const hex = Buffer.concat([signed.r, signed.s, Buffer.from([Number(signed.v)])]).toString('hex') pseudoCallback(null, addHexPrefix(hex)) } signTypedData(key, typedMessage, pseudoCallback) { try { const { data, version } = typedMessage const signature = signTypedData({ privateKey: key, data, version }) pseudoCallback(null, signature) } catch (e) { pseudoCallback(e.message) } } signTransaction(key, rawTx, pseudoCallback) { if (!rawTx.chainId) { console.error(`invalid chain id ${rawTx.chainId} for transaction`) return pseudoCallback('could not determine chain id for transaction') } const chainId = parseInt(rawTx.chainId, 16) const hardfork = parseInt(rawTx.type) === 2 ? 'london' : 'berlin' const common = chainConfig(chainId, hardfork) const tx = TransactionFactory.fromTxData(rawTx, { common }) const signedTx = tx.sign(key) const serialized = signedTx.serialize().toString('hex') pseudoCallback(null, addHexPrefix(serialized)) } verifyAddress({ index, address }, pseudoCallback) { const message = '0x' + crypto.randomBytes(32).toString('hex') this.signMessage({ index, message }, (err, signedMessage) => { // Handle signing errors if (err) return pseudoCallback(err) // Signature -> buffer const signature = Buffer.from(signedMessage.replace('0x', ''), 'hex') // Ensure correct length if (signature.length !== 65) return pseudoCallback(new Error('Frame verifyAddress signature has incorrect length')) // Verify address let v = signature[64] v = BigInt(v === 0 || v === 1 ? v + 27 : v) const r = toBuffer(signature.slice(0, 32)) const s = toBuffer(signature.slice(32, 64)) const hash = hashPersonalMessage(toBuffer(message)) const verifiedAddress = '0x' + pubToAddress(ecrecover(hash, v, r, s)).toString('hex') // Return result pseudoCallback(null, verifiedAddress.toLowerCase() === address.toLowerCase()) }) } _encrypt(string, password) { const salt = crypto.randomBytes(16) const iv = crypto.randomBytes(16) const cipher = crypto.createCipheriv('aes-256-cbc', this._hashPassword(password, salt), iv) const encrypted = Buffer.concat([cipher.update(string), cipher.final()]) return salt.toString('hex') + ':' + iv.toString('hex') + ':' + encrypted.toString('hex') } _decrypt(string, password) { const parts = string.split(':') const salt = Buffer.from(parts.shift(), 'hex') const iv = Buffer.from(parts.shift(), 'hex') const decipher = crypto.createDecipheriv('aes-256-cbc', this._hashPassword(password, salt), iv) const encryptedString = Buffer.from(parts.join(':'), 'hex') const decrypted = Buffer.concat([decipher.update(encryptedString), decipher.final()]) return decrypted.toString() } _hashPassword(password, salt) { try { return crypto.scryptSync(password, salt, 32, { N: 32768, r: 8, p: 1, maxmem: 36000000 }) } catch (e) { console.error('Error during hashPassword', e) // TODO: Handle Error } } } module.exports = HotSignerWorker