/* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. web3.js is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ /** * @file soliditySha3.js * @author Fabian Vogelsteller * @date 2017 */ var BN = require('bn.js'); var utils = require('./utils.js'); var _elementaryName = function (name) { /*jshint maxcomplexity:false */ if (name.startsWith('int[')) { return 'int256' + name.slice(3); } else if (name === 'int') { return 'int256'; } else if (name.startsWith('uint[')) { return 'uint256' + name.slice(4); } else if (name === 'uint') { return 'uint256'; } else if (name.startsWith('fixed[')) { return 'fixed128x128' + name.slice(5); } else if (name === 'fixed') { return 'fixed128x128'; } else if (name.startsWith('ufixed[')) { return 'ufixed128x128' + name.slice(6); } else if (name === 'ufixed') { return 'ufixed128x128'; } return name; }; // Parse N from type var _parseTypeN = function (type) { var typesize = /^\D+(\d+).*$/.exec(type); return typesize ? parseInt(typesize[1], 10) : null; }; // Parse N from type[] var _parseTypeNArray = function (type) { var arraySize = /^\D+\d*\[(\d+)\]$/.exec(type); return arraySize ? parseInt(arraySize[1], 10) : null; }; var _parseNumber = function (arg) { var type = typeof arg; if (type === 'string') { if (utils.isHexStrict(arg)) { return new BN(arg.replace(/0x/i, ''), 16); } else { return new BN(arg, 10); } } else if (type === 'number') { return new BN(arg); } else if (utils.isBigNumber(arg)) { return new BN(arg.toString(10)); } else if (utils.isBN(arg)) { return arg; } else { throw new Error(arg + ' is not a number'); } }; var _solidityPack = function (type, value, arraySize) { /*jshint maxcomplexity:false */ var size, num; type = _elementaryName(type); if (type === 'bytes') { if (value.replace(/^0x/i, '').length % 2 !== 0) { throw new Error('Invalid bytes characters ' + value.length); } return value; } else if (type === 'string') { return utils.utf8ToHex(value); } else if (type === 'bool') { return value ? '01' : '00'; } else if (type.startsWith('address')) { if (arraySize) { size = 64; } else { size = 40; } if (!utils.isAddress(value)) { throw new Error(value + ' is not a valid address, or the checksum is invalid.'); } return utils.leftPad(value.toLowerCase(), size); } size = _parseTypeN(type); if (type.startsWith('bytes')) { if (!size) { throw new Error('bytes[] not yet supported in solidity'); } // must be 32 byte slices when in an array if (arraySize) { size = 32; } if (size < 1 || size > 32 || size < value.replace(/^0x/i, '').length / 2) { throw new Error('Invalid bytes' + size + ' for ' + value); } return utils.rightPad(value, size * 2); } else if (type.startsWith('uint')) { if ((size % 8) || (size < 8) || (size > 256)) { throw new Error('Invalid uint' + size + ' size'); } num = _parseNumber(value); if (num.bitLength() > size) { throw new Error('Supplied uint exceeds width: ' + size + ' vs ' + num.bitLength()); } if (num.lt(new BN(0))) { throw new Error('Supplied uint ' + num.toString() + ' is negative'); } return size ? utils.leftPad(num.toString('hex'), size / 8 * 2) : num; } else if (type.startsWith('int')) { if ((size % 8) || (size < 8) || (size > 256)) { throw new Error('Invalid int' + size + ' size'); } num = _parseNumber(value); if (num.bitLength() > size) { throw new Error('Supplied int exceeds width: ' + size + ' vs ' + num.bitLength()); } if (num.lt(new BN(0))) { return num.toTwos(size).toString('hex'); } else { return size ? utils.leftPad(num.toString('hex'), size / 8 * 2) : num; } } else { // FIXME: support all other types throw new Error('Unsupported or invalid type: ' + type); } }; var _processSolidityEncodePackedArgs = function (arg) { /*jshint maxcomplexity:false */ if (Array.isArray(arg)) { throw new Error('Autodetection of array types is not supported.'); } var type, value = ''; var hexArg, arraySize; // if type is given if (!!arg && typeof arg === 'object' && (arg.hasOwnProperty('v') || arg.hasOwnProperty('t') || arg.hasOwnProperty('value') || arg.hasOwnProperty('type'))) { type = arg.hasOwnProperty('t') ? arg.t : arg.type; value = arg.hasOwnProperty('v') ? arg.v : arg.value; // otherwise try to guess the type } else { type = utils.toHex(arg, true); value = utils.toHex(arg); if (!type.startsWith('int') && !type.startsWith('uint')) { type = 'bytes'; } } if ((type.startsWith('int') || type.startsWith('uint')) && typeof value === 'string' && !/^(-)?0x/i.test(value)) { value = new BN(value); } // get the array size if (Array.isArray(value)) { arraySize = _parseTypeNArray(type); if (arraySize && value.length !== arraySize) { throw new Error(type + ' is not matching the given array ' + JSON.stringify(value)); } else { arraySize = value.length; } } if (Array.isArray(value)) { hexArg = value.map(function (val) { return _solidityPack(type, val, arraySize).toString('hex').replace('0x', ''); }); return hexArg.join(''); } else { hexArg = _solidityPack(type, value, arraySize); return hexArg.toString('hex').replace('0x', ''); } }; /** * Hashes solidity values to a sha3 hash using keccak 256 * * @method soliditySha3 * @return {Object} the sha3 */ var soliditySha3 = function () { /*jshint maxcomplexity:false */ var args = Array.prototype.slice.call(arguments); var hexArgs = args.map(_processSolidityEncodePackedArgs); // console.log(args, hexArgs); // console.log('0x'+ hexArgs.join('')); return utils.sha3('0x' + hexArgs.join('')); }; /** * Hashes solidity values to a sha3 hash using keccak 256 but does return the hash of value `null` instead of `null` * * @method soliditySha3Raw * @return {Object} the sha3 */ var soliditySha3Raw = function () { return utils.sha3Raw('0x' + Array.prototype.slice.call(arguments).map(_processSolidityEncodePackedArgs).join('')); }; /** * Encode packed args to hex * * @method encodePacked * @return {String} the hex encoded arguments */ var encodePacked = function () { /*jshint maxcomplexity:false */ var args = Array.prototype.slice.call(arguments); var hexArgs = args.map(_processSolidityEncodePackedArgs); return '0x' + hexArgs.join('').toLowerCase(); }; module.exports = { soliditySha3: soliditySha3, soliditySha3Raw: soliditySha3Raw, encodePacked: encodePacked };