#!/usr/bin/env node const decode = require('qr-encoding').decode const {stripHexPrefix, toBuffer, toRpcSig} = require('@ethereumjs/util') const fs = require('fs') const QRCode = require('qrcode') const sigUtil = require('@metamask/eth-sig-util') const {Transaction} = require('@ethereumjs/tx') const keythereum = require("keythereum") const inquirer = require('inquirer') const {Common} = require('@ethereumjs/common') var api const arg = process.argv[2] if (!arg) { // fixed bug termux hang during loading api = require('termux') } const ai = '/data/data/com.termux/files/home/airsign/' if (arg) { try { // `airsign ETHS:/T101105...` called sign(arg) } catch (e) { if (!arg.match(/-/)) { console.log(`Could not decode.`) } // `airsign -` called inquirer.prompt([ { type: 'input', name: 'qr', message: 'Signable:', validate (value) { try { decode(value) return true } catch (e) { return 'Should be something like: ETHS:/T001011....' } }, filter: String, } ]).then((answers) => { sign(answers.qr) }) } } else if (api.hasTermux) { api.clipboardGet() .run() .then(sign) } function sign (text) { text = text.replace(/^\\s*/, '').replace(/\\s*$/, '') var sigParams = decode(text) var fromJson, signature, rawTx, tx, common var keyStore = ai + '.ethereum/keystore/' var re = new RegExp(sigParams.from_part.toLowerCase()) fs.readdir(keyStore, (err, files) => { if (err) { throw new Error(err.stack); } fromJson = files .map(file => { try { return JSON.parse(fs.readFileSync(keyStore + file)) } catch (e) { return {address: ''} } }) .find(json => json.address .replace(/^/, '0x') .toLowerCase() .match(re) ) if (!fromJson) throw new Error('Address not found for: ' + sigParams.from_part) inquirer.prompt([ { type: 'password', name: 'pw', message: 'Password:', mask: '*', } ]).then( (answer) => { var privateKey = keythereum.recover(answer.pw, fromJson) answer.pw = null switch (sigParams.type) { case 'sign_transaction': rawTx = { from: '0x' + stripHexPrefix(fromJson.address), to: sigParams.payload.to, gasPrice: sigParams.payload.gasPrice, gasLimit: sigParams.payload.gasLimit, chainId: sigParams.payload.chainId, nonce: sigParams.payload.nonce, value: sigParams.payload.value, data: sigParams.payload.data, } common = new Common({chain: rawTx.chainId}) tx = Transaction.fromTxData(rawTx, {common}) tx = tx.sign(privateKey) signature = toRpcSig( tx.v, toBuffer(tx.r === '' ? '0x' : tx.r), toBuffer(tx.s === '' ? '0x' : tx.s), BigInt(rawTx.chainId) ) break case 'sign_message': signature = sigUtil.personalSign({ privateKey, data: '0x' + stripHexPrefix(sigParams.payload), }) break case 'sign_personal_message': signature = sigUtil.personalSign({ privateKey, data: '0x' + stripHexPrefix(sigParams.payload), }) break case 'sign_typed_data': signature = sigUtil.signTypedData({ privateKey, data: JSON.parse(stripHexPrefix(sigParams.payload)), version: sigParams.version, }) break default: throw new Error('Not supported: ' + sigParams.type) } privateKey = null const types = ['terminal', 'utf8', 'svg'] types.forEach(type => { QRCode.toString(signature, {type}, (err, str) => { if (err) throw new Error(err) fs.writeFileSync(`qr.${type}`, Buffer.from(str)) }) }) var type = 'terminal' while (type) { QRCode.toString(signature, {type}, (err, str) => { if (err) throw new Error(err) console.log(str) console.log(signature) require('child_process').spawnSync("read _ ", {shell: true, stdio: [0, 1, 2]}); }) } }) .catch((error) => { throw new Error( `Inquirer failure: ${error.stack}`) }) }) }