244 lines
7.7 KiB
JavaScript
244 lines
7.7 KiB
JavaScript
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
/**
|
|
* @file soliditySha3.js
|
|
* @author Fabian Vogelsteller <fabian@ethereum.org>
|
|
* @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<N>
|
|
var _parseTypeN = function (type) {
|
|
var typesize = /^\D+(\d+).*$/.exec(type);
|
|
return typesize ? parseInt(typesize[1], 10) : null;
|
|
};
|
|
// Parse N from type[<N>]
|
|
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
|
|
};
|