/*
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 utils.js
* @author Fabian Vogelsteller
* @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
};