503 lines
15 KiB
JavaScript
503 lines
15 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 utils.js
|
|
* @author Fabian Vogelsteller <fabian@ethereum.org>
|
|
* @date 2017
|
|
*/
|
|
var BN = require('bn.js');
|
|
var numberToBN = require('number-to-bn');
|
|
var utf8 = require('utf8');
|
|
var ethereumjsUtil = require('ethereumjs-util');
|
|
var ethereumBloomFilters = require('ethereum-bloom-filters');
|
|
/**
|
|
* Returns true if object is BN, otherwise false
|
|
*
|
|
* @method isBN
|
|
* @param {Object} object
|
|
* @return {Boolean}
|
|
*/
|
|
var isBN = function (object) {
|
|
return BN.isBN(object);
|
|
};
|
|
/**
|
|
* Returns true if object is BigNumber, otherwise false
|
|
*
|
|
* @method isBigNumber
|
|
* @param {Object} object
|
|
* @return {Boolean}
|
|
*/
|
|
var isBigNumber = function (object) {
|
|
return object && object.constructor && object.constructor.name === 'BigNumber';
|
|
};
|
|
/**
|
|
* Takes an input and transforms it into an BN
|
|
*
|
|
* @method toBN
|
|
* @param {Number|String|BN} number, string, HEX string or BN
|
|
* @return {BN} BN
|
|
*/
|
|
var toBN = function (number) {
|
|
try {
|
|
return numberToBN.apply(null, arguments);
|
|
}
|
|
catch (e) {
|
|
throw new Error(e + ' Given value: "' + number + '"');
|
|
}
|
|
};
|
|
/**
|
|
* Takes and input transforms it into BN and if it is negative value, into two's complement
|
|
*
|
|
* @method toTwosComplement
|
|
* @param {Number|String|BN} number
|
|
* @return {String}
|
|
*/
|
|
var toTwosComplement = function (number) {
|
|
return '0x' + toBN(number).toTwos(256).toString(16, 64);
|
|
};
|
|
/**
|
|
* Checks if the given string is an address
|
|
*
|
|
* @method isAddress
|
|
* @param {String} address the given HEX address
|
|
* @return {Boolean}
|
|
*/
|
|
var isAddress = function (address) {
|
|
// check if it has the basic requirements of an address
|
|
if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) {
|
|
return false;
|
|
// If it's ALL lowercase or ALL upppercase
|
|
}
|
|
else if (/^(0x|0X)?[0-9a-f]{40}$/.test(address) || /^(0x|0X)?[0-9A-F]{40}$/.test(address)) {
|
|
return true;
|
|
// Otherwise check each case
|
|
}
|
|
else {
|
|
return checkAddressChecksum(address);
|
|
}
|
|
};
|
|
/**
|
|
* Checks if the given string is a checksummed address
|
|
*
|
|
* @method checkAddressChecksum
|
|
* @param {String} address the given HEX address
|
|
* @return {Boolean}
|
|
*/
|
|
var checkAddressChecksum = function (address) {
|
|
// Check each case
|
|
address = address.replace(/^0x/i, '');
|
|
var addressHash = sha3(address.toLowerCase()).replace(/^0x/i, '');
|
|
for (var i = 0; i < 40; i++) {
|
|
// the nth letter should be uppercase if the nth digit of casemap is 1
|
|
if ((parseInt(addressHash[i], 16) > 7 && address[i].toUpperCase() !== address[i]) || (parseInt(addressHash[i], 16) <= 7 && address[i].toLowerCase() !== address[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
/**
|
|
* Should be called to pad string to expected length
|
|
*
|
|
* @method leftPad
|
|
* @param {String} string to be padded
|
|
* @param {Number} chars that result string should have
|
|
* @param {String} sign, by default 0
|
|
* @returns {String} right aligned string
|
|
*/
|
|
var leftPad = function (string, chars, sign) {
|
|
var hasPrefix = /^0x/i.test(string) || typeof string === 'number';
|
|
string = string.toString(16).replace(/^0x/i, '');
|
|
var padding = (chars - string.length + 1 >= 0) ? chars - string.length + 1 : 0;
|
|
return (hasPrefix ? '0x' : '') + new Array(padding).join(sign ? sign : "0") + string;
|
|
};
|
|
/**
|
|
* Should be called to pad string to expected length
|
|
*
|
|
* @method rightPad
|
|
* @param {String} string to be padded
|
|
* @param {Number} chars that result string should have
|
|
* @param {String} sign, by default 0
|
|
* @returns {String} right aligned string
|
|
*/
|
|
var rightPad = function (string, chars, sign) {
|
|
var hasPrefix = /^0x/i.test(string) || typeof string === 'number';
|
|
string = string.toString(16).replace(/^0x/i, '');
|
|
var padding = (chars - string.length + 1 >= 0) ? chars - string.length + 1 : 0;
|
|
return (hasPrefix ? '0x' : '') + string + (new Array(padding).join(sign ? sign : "0"));
|
|
};
|
|
/**
|
|
* Should be called to get hex representation (prefixed by 0x) of utf8 string
|
|
*
|
|
* @method utf8ToHex
|
|
* @param {String} str
|
|
* @returns {String} hex representation of input string
|
|
*/
|
|
var utf8ToHex = function (str) {
|
|
str = utf8.encode(str);
|
|
var hex = "";
|
|
// remove \u0000 padding from either side
|
|
str = str.replace(/^(?:\u0000)*/, '');
|
|
str = str.split("").reverse().join("");
|
|
str = str.replace(/^(?:\u0000)*/, '');
|
|
str = str.split("").reverse().join("");
|
|
for (var i = 0; i < str.length; i++) {
|
|
var code = str.charCodeAt(i);
|
|
// if (code !== 0) {
|
|
var n = code.toString(16);
|
|
hex += n.length < 2 ? '0' + n : n;
|
|
// }
|
|
}
|
|
return "0x" + hex;
|
|
};
|
|
/**
|
|
* Should be called to get utf8 from it's hex representation
|
|
*
|
|
* @method hexToUtf8
|
|
* @param {String} hex
|
|
* @returns {String} ascii string representation of hex value
|
|
*/
|
|
var hexToUtf8 = function (hex) {
|
|
if (!isHexStrict(hex))
|
|
throw new Error('The parameter "' + hex + '" must be a valid HEX string.');
|
|
var str = "";
|
|
var code = 0;
|
|
hex = hex.replace(/^0x/i, '');
|
|
// remove 00 padding from either side
|
|
hex = hex.replace(/^(?:00)*/, '');
|
|
hex = hex.split("").reverse().join("");
|
|
hex = hex.replace(/^(?:00)*/, '');
|
|
hex = hex.split("").reverse().join("");
|
|
var l = hex.length;
|
|
for (var i = 0; i < l; i += 2) {
|
|
code = parseInt(hex.substr(i, 2), 16);
|
|
// if (code !== 0) {
|
|
str += String.fromCharCode(code);
|
|
// }
|
|
}
|
|
return utf8.decode(str);
|
|
};
|
|
/**
|
|
* Converts value to it's number representation
|
|
*
|
|
* @method hexToNumber
|
|
* @param {String|Number|BN} value
|
|
* @return {String}
|
|
*/
|
|
var hexToNumber = function (value) {
|
|
if (!value) {
|
|
return value;
|
|
}
|
|
if (typeof value === 'string' && !isHexStrict(value)) {
|
|
throw new Error('Given value "' + value + '" is not a valid hex string.');
|
|
}
|
|
return toBN(value).toNumber();
|
|
};
|
|
/**
|
|
* Converts value to it's decimal representation in string
|
|
*
|
|
* @method hexToNumberString
|
|
* @param {String|Number|BN} value
|
|
* @return {String}
|
|
*/
|
|
var hexToNumberString = function (value) {
|
|
if (!value)
|
|
return value;
|
|
if (typeof value === 'string' && !isHexStrict(value)) {
|
|
throw new Error('Given value "' + value + '" is not a valid hex string.');
|
|
}
|
|
return toBN(value).toString(10);
|
|
};
|
|
/**
|
|
* Converts value to it's hex representation
|
|
*
|
|
* @method numberToHex
|
|
* @param {String|Number|BN} value
|
|
* @return {String}
|
|
*/
|
|
var numberToHex = function (value) {
|
|
if ((value === null || value === undefined)) {
|
|
return value;
|
|
}
|
|
if (!isFinite(value) && !isHexStrict(value)) {
|
|
throw new Error('Given input "' + value + '" is not a number.');
|
|
}
|
|
var number = toBN(value);
|
|
var result = number.toString(16);
|
|
return number.lt(new BN(0)) ? '-0x' + result.substr(1) : '0x' + result;
|
|
};
|
|
/**
|
|
* Convert a byte array to a hex string
|
|
*
|
|
* Note: Implementation from crypto-js
|
|
*
|
|
* @method bytesToHex
|
|
* @param {Array} bytes
|
|
* @return {String} the hex string
|
|
*/
|
|
var bytesToHex = function (bytes) {
|
|
for (var hex = [], i = 0; i < bytes.length; i++) {
|
|
/* jshint ignore:start */
|
|
hex.push((bytes[i] >>> 4).toString(16));
|
|
hex.push((bytes[i] & 0xF).toString(16));
|
|
/* jshint ignore:end */
|
|
}
|
|
return '0x' + hex.join("");
|
|
};
|
|
/**
|
|
* Convert a hex string to a byte array
|
|
*
|
|
* Note: Implementation from crypto-js
|
|
*
|
|
* @method hexToBytes
|
|
* @param {string} hex
|
|
* @return {Array} the byte array
|
|
*/
|
|
var hexToBytes = function (hex) {
|
|
hex = hex.toString(16);
|
|
if (!isHexStrict(hex)) {
|
|
throw new Error('Given value "' + hex + '" is not a valid hex string.');
|
|
}
|
|
hex = hex.replace(/^0x/i, '');
|
|
for (var bytes = [], c = 0; c < hex.length; c += 2)
|
|
bytes.push(parseInt(hex.substr(c, 2), 16));
|
|
return bytes;
|
|
};
|
|
/**
|
|
* Auto converts any given value into it's hex representation.
|
|
*
|
|
* And even stringifys objects before.
|
|
*
|
|
* @method toHex
|
|
* @param {String|Number|BN|Object|Buffer} value
|
|
* @param {Boolean} returnType
|
|
* @return {String}
|
|
*/
|
|
var toHex = function (value, returnType) {
|
|
/*jshint maxcomplexity: false */
|
|
if (isAddress(value)) {
|
|
return returnType ? 'address' : '0x' + value.toLowerCase().replace(/^0x/i, '');
|
|
}
|
|
if (typeof value === 'boolean') {
|
|
return returnType ? 'bool' : value ? '0x01' : '0x00';
|
|
}
|
|
if (Buffer.isBuffer(value)) {
|
|
return '0x' + value.toString('hex');
|
|
}
|
|
if (typeof value === 'object' && !!value && !isBigNumber(value) && !isBN(value)) {
|
|
return returnType ? 'string' : utf8ToHex(JSON.stringify(value));
|
|
}
|
|
// if its a negative number, pass it through numberToHex
|
|
if (typeof value === 'string') {
|
|
if (value.indexOf('-0x') === 0 || value.indexOf('-0X') === 0) {
|
|
return returnType ? 'int256' : numberToHex(value);
|
|
}
|
|
else if (value.indexOf('0x') === 0 || value.indexOf('0X') === 0) {
|
|
return returnType ? 'bytes' : value;
|
|
}
|
|
else if (!isFinite(value)) {
|
|
return returnType ? 'string' : utf8ToHex(value);
|
|
}
|
|
}
|
|
return returnType ? (value < 0 ? 'int256' : 'uint256') : numberToHex(value);
|
|
};
|
|
/**
|
|
* Check if string is HEX, requires a 0x in front
|
|
*
|
|
* @method isHexStrict
|
|
* @param {String} hex to be checked
|
|
* @returns {Boolean}
|
|
*/
|
|
var isHexStrict = function (hex) {
|
|
return ((typeof hex === 'string' || typeof hex === 'number') && /^(-)?0x[0-9a-f]*$/i.test(hex));
|
|
};
|
|
/**
|
|
* Check if string is HEX
|
|
*
|
|
* @method isHex
|
|
* @param {String} hex to be checked
|
|
* @returns {Boolean}
|
|
*/
|
|
var isHex = function (hex) {
|
|
return ((typeof hex === 'string' || typeof hex === 'number') && /^(-0x|0x)?[0-9a-f]*$/i.test(hex));
|
|
};
|
|
/**
|
|
* Remove 0x prefix from string
|
|
*
|
|
* @method stripHexPrefix
|
|
* @param {String} str to be checked
|
|
* @returns {String}
|
|
*/
|
|
var stripHexPrefix = function (str) {
|
|
if (str !== 0 && isHex(str))
|
|
return str.replace(/^(-)?0x/i, '$1');
|
|
return str;
|
|
};
|
|
/**
|
|
* Returns true if given string is a valid Ethereum block header bloom.
|
|
*
|
|
* @method isBloom
|
|
* @param {String} bloom encoded bloom filter
|
|
* @return {Boolean}
|
|
*/
|
|
var isBloom = function (bloom) {
|
|
return ethereumBloomFilters.isBloom(bloom);
|
|
};
|
|
/**
|
|
* Returns true if the ethereum users address is part of the given bloom
|
|
* note: false positives are possible.
|
|
*
|
|
* @method isUserEthereumAddressInBloom
|
|
* @param {String} ethereumAddress encoded bloom filter
|
|
* @param {String} bloom ethereum addresss
|
|
* @return {Boolean}
|
|
*/
|
|
var isUserEthereumAddressInBloom = function (bloom, ethereumAddress) {
|
|
return ethereumBloomFilters.isUserEthereumAddressInBloom(bloom, ethereumAddress);
|
|
};
|
|
/**
|
|
* Returns true if the contract address is part of the given bloom
|
|
* note: false positives are possible.
|
|
*
|
|
* @method isUserEthereumAddressInBloom
|
|
* @param {String} bloom encoded bloom filter
|
|
* @param {String} contractAddress contract addresss
|
|
* @return {Boolean}
|
|
*/
|
|
var isContractAddressInBloom = function (bloom, contractAddress) {
|
|
return ethereumBloomFilters.isContractAddressInBloom(bloom, contractAddress);
|
|
};
|
|
/**
|
|
* Returns true if given string is a valid log topic.
|
|
*
|
|
* @method isTopic
|
|
* @param {String} topic encoded topic
|
|
* @return {Boolean}
|
|
*/
|
|
var isTopic = function (topic) {
|
|
return ethereumBloomFilters.isTopic(topic);
|
|
};
|
|
/**
|
|
* Returns true if the topic is part of the given bloom
|
|
* note: false positives are possible.
|
|
*
|
|
* @method isTopicInBloom
|
|
* @param {String} bloom encoded bloom filter
|
|
* @param {String} topic encoded topic
|
|
* @return {Boolean}
|
|
*/
|
|
var isTopicInBloom = function (bloom, topic) {
|
|
return ethereumBloomFilters.isTopicInBloom(bloom, topic);
|
|
};
|
|
/**
|
|
* Returns true if the value is part of the given bloom
|
|
* note: false positives are possible.
|
|
*
|
|
* @method isInBloom
|
|
* @param {String} bloom encoded bloom filter
|
|
* @param {String | Uint8Array} topic encoded value
|
|
* @return {Boolean}
|
|
*/
|
|
var isInBloom = function (bloom, topic) {
|
|
return ethereumBloomFilters.isInBloom(bloom, topic);
|
|
};
|
|
/**
|
|
* Hashes values to a sha3 hash using keccak 256
|
|
*
|
|
* To hash a HEX string the hex must have 0x in front.
|
|
*
|
|
* @method sha3
|
|
* @return {String} the sha3 string
|
|
*/
|
|
var SHA3_NULL_S = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470';
|
|
var sha3 = function (value) {
|
|
if (isBN(value)) {
|
|
value = value.toString();
|
|
}
|
|
if (isHexStrict(value) && /^0x/i.test((value).toString())) {
|
|
value = ethereumjsUtil.toBuffer(value);
|
|
}
|
|
else if (typeof value === 'string') {
|
|
// Assume value is an arbitrary string
|
|
value = Buffer.from(value, 'utf-8');
|
|
}
|
|
var returnValue = ethereumjsUtil.bufferToHex(ethereumjsUtil.keccak256(value));
|
|
if (returnValue === SHA3_NULL_S) {
|
|
return null;
|
|
}
|
|
else {
|
|
return returnValue;
|
|
}
|
|
};
|
|
// expose the under the hood keccak256
|
|
sha3._Hash = ethereumjsUtil.keccak256;
|
|
/**
|
|
* @method sha3Raw
|
|
*
|
|
* @param value
|
|
*
|
|
* @returns {string}
|
|
*/
|
|
var sha3Raw = function (value) {
|
|
value = sha3(value);
|
|
if (value === null) {
|
|
return SHA3_NULL_S;
|
|
}
|
|
return value;
|
|
};
|
|
/**
|
|
* Auto converts any given value into it's hex representation,
|
|
* then converts hex to number.
|
|
*
|
|
* @method toNumber
|
|
* @param {String|Number|BN} value
|
|
* @return {Number}
|
|
*/
|
|
var toNumber = function (value) {
|
|
return typeof value === 'number' ? value : hexToNumber(toHex(value));
|
|
};
|
|
module.exports = {
|
|
BN: BN,
|
|
isBN: isBN,
|
|
isBigNumber: isBigNumber,
|
|
toBN: toBN,
|
|
isAddress: isAddress,
|
|
isBloom: isBloom,
|
|
isUserEthereumAddressInBloom: isUserEthereumAddressInBloom,
|
|
isContractAddressInBloom: isContractAddressInBloom,
|
|
isTopic: isTopic,
|
|
isTopicInBloom: isTopicInBloom,
|
|
isInBloom: isInBloom,
|
|
checkAddressChecksum: checkAddressChecksum,
|
|
utf8ToHex: utf8ToHex,
|
|
hexToUtf8: hexToUtf8,
|
|
hexToNumber: hexToNumber,
|
|
hexToNumberString: hexToNumberString,
|
|
numberToHex: numberToHex,
|
|
toHex: toHex,
|
|
hexToBytes: hexToBytes,
|
|
bytesToHex: bytesToHex,
|
|
isHex: isHex,
|
|
isHexStrict: isHexStrict,
|
|
stripHexPrefix: stripHexPrefix,
|
|
leftPad: leftPad,
|
|
rightPad: rightPad,
|
|
toTwosComplement: toTwosComplement,
|
|
sha3: sha3,
|
|
sha3Raw: sha3Raw,
|
|
toNumber: toNumber
|
|
};
|