UNPKG

@steemit/steem-js

Version:

JavaScript library for the Steem blockchain

135 lines (130 loc) 3.99 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.K = void 0; exports.sign = sign; exports.validate = validate; var _crypto = require("crypto"); var _ecc = require("../auth/ecc"); /** * @file JSONRPC 2.0 request authentication with steem authorities. * Based on the original @steemit/rpc-auth package */ /** * Signing constant used to reserve opcode space and prevent cross-protocol attacks. * Output of `sha256('steem_jsonrpc_auth')`. */ const K = exports.K = Buffer.from('3b3b081e46ea808d5a96b08c4bc5003f5e15767090f344faab531ec57565136b', 'hex'); /** * Create request hash to be signed. * * @param timestamp ISO8601 formatted date e.g. `2017-11-14T19:40:29.077Z`. * @param account Steem account name that is the signer. * @param method RPC request method. * @param params Base64 encoded JSON string containing request params. * @param nonce 8 bytes of random data. * * @returns bytes to be signed or validated. */ function hashMessage(timestamp, account, method, params, nonce) { const first = (0, _crypto.createHash)('sha256'); first.update(timestamp); first.update(account); first.update(method); first.update(params); const second = (0, _crypto.createHash)('sha256'); second.update(K); second.update(first.digest()); second.update(nonce); return second.digest(); } /** * Sign a JSON RPC Request. */ function sign(request, account, keys) { if (!request.params) { throw new Error('Unable to sign a request without params'); } const params = Buffer.from(JSON.stringify(request.params), 'utf8').toString('base64'); const nonceBytes = (0, _crypto.randomBytes)(8); const nonce = nonceBytes.toString('hex'); const timestamp = new Date().toISOString(); const message = hashMessage(timestamp, account, request.method, params, nonceBytes); const signatures = []; for (let key of keys) { if (typeof key === 'string') { key = _ecc.PrivateKey.fromString(key); } const signature = key.sign(message).toHex(); signatures.push(signature); } return { jsonrpc: '2.0', method: request.method, id: request.id, params: { __signed: { account, nonce, params, signatures, timestamp } } }; } /** * Validate a signed JSON RPC request. * Throws a ValidationError if the request fails validation. * * @returns Resolved request params. */ async function validate(request, verify) { if (request.jsonrpc !== '2.0' || typeof request.method !== 'string') { throw new Error('Invalid JSON RPC Request'); } if (request.params == undefined || request.params.__signed == undefined) { throw new Error('Signed payload missing'); } if (Object.keys(request.params).length !== 1) { throw new Error('Invalid request params'); } const signed = request.params.__signed; if (signed.account == undefined) { throw new Error('Missing account'); } let params; try { const jsonString = Buffer.from(signed.params, 'base64').toString('utf8'); params = JSON.parse(jsonString); } catch (cause) { throw new Error(`Invalid encoded params: ${cause.message}`); } if (signed.nonce == undefined || typeof signed.nonce !== 'string') { throw new Error('Invalid nonce'); } const nonce = Buffer.from(signed.nonce, 'hex'); if (nonce.length !== 8) { throw new Error('Invalid nonce'); } const timestamp = Date.parse(signed.timestamp); if (Number.isNaN(timestamp)) { throw new Error('Invalid timestamp'); } if (Date.now() - timestamp > 60 * 1000) { throw new Error('Signature expired'); } const message = hashMessage(signed.timestamp, signed.account, request.method, signed.params, nonce); try { await verify(message, signed.signatures, signed.account); } catch (cause) { throw new Error(`Verification failed: ${cause.message}`); } return params; } var _default = exports.default = { sign, validate, K };