UNPKG

12.1 kBJavaScriptView Raw
1/*
2 This file is part of web3.js.
3
4 web3.js is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 web3.js is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public License
15 along with web3.js. If not, see <http://www.gnu.org/licenses/>.
16 */
17/**
18 * @file utils.js
19 * @author Fabian Vogelsteller <fabian@ethereum.org>
20 * @date 2017
21 */
22
23var _ = require('underscore');
24var BN = require('bn.js');
25var numberToBN = require('number-to-bn');
26var utf8 = require('utf8');
27var Hash = require("eth-lib/lib/hash");
28
29
30/**
31 * Returns true if object is BN, otherwise false
32 *
33 * @method isBN
34 * @param {Object} object
35 * @return {Boolean}
36 */
37var isBN = function (object) {
38 return object instanceof BN ||
39 (object && object.constructor && object.constructor.name === 'BN');
40};
41
42/**
43 * Returns true if object is BigNumber, otherwise false
44 *
45 * @method isBigNumber
46 * @param {Object} object
47 * @return {Boolean}
48 */
49var isBigNumber = function (object) {
50 return object && object.constructor && object.constructor.name === 'BigNumber';
51};
52
53/**
54 * Takes an input and transforms it into an BN
55 *
56 * @method toBN
57 * @param {Number|String|BN} number, string, HEX string or BN
58 * @return {BN} BN
59 */
60var toBN = function(number){
61 try {
62 return numberToBN.apply(null, arguments);
63 } catch(e) {
64 throw new Error(e + ' Given value: "'+ number +'"');
65 }
66};
67
68
69/**
70 * Takes and input transforms it into BN and if it is negative value, into two's complement
71 *
72 * @method toTwosComplement
73 * @param {Number|String|BN} number
74 * @return {String}
75 */
76var toTwosComplement = function (number) {
77 return '0x'+ toBN(number).toTwos(256).toString(16, 64);
78};
79
80/**
81 * Checks if the given string is an address
82 *
83 * @method isAddress
84 * @param {String} address the given HEX address
85 * @return {Boolean}
86 */
87var isAddress = function (address) {
88 // check if it has the basic requirements of an address
89 if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) {
90 return false;
91 // If it's ALL lowercase or ALL upppercase
92 } else if (/^(0x|0X)?[0-9a-f]{40}$/.test(address) || /^(0x|0X)?[0-9A-F]{40}$/.test(address)) {
93 return true;
94 // Otherwise check each case
95 } else {
96 return checkAddressChecksum(address);
97 }
98};
99
100
101
102/**
103 * Checks if the given string is a checksummed address
104 *
105 * @method checkAddressChecksum
106 * @param {String} address the given HEX address
107 * @return {Boolean}
108 */
109var checkAddressChecksum = function (address) {
110 // Check each case
111 address = address.replace(/^0x/i,'');
112 var addressHash = sha3(address.toLowerCase()).replace(/^0x/i,'');
113
114 for (var i = 0; i < 40; i++ ) {
115 // the nth letter should be uppercase if the nth digit of casemap is 1
116 if ((parseInt(addressHash[i], 16) > 7 && address[i].toUpperCase() !== address[i]) || (parseInt(addressHash[i], 16) <= 7 && address[i].toLowerCase() !== address[i])) {
117 return false;
118 }
119 }
120 return true;
121};
122
123/**
124 * Should be called to pad string to expected length
125 *
126 * @method leftPad
127 * @param {String} string to be padded
128 * @param {Number} chars that result string should have
129 * @param {String} sign, by default 0
130 * @returns {String} right aligned string
131 */
132var leftPad = function (string, chars, sign) {
133 var hasPrefix = /^0x/i.test(string) || typeof string === 'number';
134 string = string.toString(16).replace(/^0x/i,'');
135
136 var padding = (chars - string.length + 1 >= 0) ? chars - string.length + 1 : 0;
137
138 return (hasPrefix ? '0x' : '') + new Array(padding).join(sign ? sign : "0") + string;
139};
140
141/**
142 * Should be called to pad string to expected length
143 *
144 * @method rightPad
145 * @param {String} string to be padded
146 * @param {Number} chars that result string should have
147 * @param {String} sign, by default 0
148 * @returns {String} right aligned string
149 */
150var rightPad = function (string, chars, sign) {
151 var hasPrefix = /^0x/i.test(string) || typeof string === 'number';
152 string = string.toString(16).replace(/^0x/i,'');
153
154 var padding = (chars - string.length + 1 >= 0) ? chars - string.length + 1 : 0;
155
156 return (hasPrefix ? '0x' : '') + string + (new Array(padding).join(sign ? sign : "0"));
157};
158
159
160/**
161 * Should be called to get hex representation (prefixed by 0x) of utf8 string
162 *
163 * @method utf8ToHex
164 * @param {String} str
165 * @returns {String} hex representation of input string
166 */
167var utf8ToHex = function(str) {
168 str = utf8.encode(str);
169 var hex = "";
170
171 // remove \u0000 padding from either side
172 str = str.replace(/^(?:\u0000)*/,'');
173 str = str.split("").reverse().join("");
174 str = str.replace(/^(?:\u0000)*/,'');
175 str = str.split("").reverse().join("");
176
177 for(var i = 0; i < str.length; i++) {
178 var code = str.charCodeAt(i);
179 // if (code !== 0) {
180 var n = code.toString(16);
181 hex += n.length < 2 ? '0' + n : n;
182 // }
183 }
184
185 return "0x" + hex;
186};
187
188/**
189 * Should be called to get utf8 from it's hex representation
190 *
191 * @method hexToUtf8
192 * @param {String} hex
193 * @returns {String} ascii string representation of hex value
194 */
195var hexToUtf8 = function(hex) {
196 if (!isHexStrict(hex))
197 throw new Error('The parameter "'+ hex +'" must be a valid HEX string.');
198
199 var str = "";
200 var code = 0;
201 hex = hex.replace(/^0x/i,'');
202
203 // remove 00 padding from either side
204 hex = hex.replace(/^(?:00)*/,'');
205 hex = hex.split("").reverse().join("");
206 hex = hex.replace(/^(?:00)*/,'');
207 hex = hex.split("").reverse().join("");
208
209 var l = hex.length;
210
211 for (var i=0; i < l; i+=2) {
212 code = parseInt(hex.substr(i, 2), 16);
213 // if (code !== 0) {
214 str += String.fromCharCode(code);
215 // }
216 }
217
218 return utf8.decode(str);
219};
220
221
222/**
223 * Converts value to it's number representation
224 *
225 * @method hexToNumber
226 * @param {String|Number|BN} value
227 * @return {String}
228 */
229var hexToNumber = function (value) {
230 if (!value) {
231 return value;
232 }
233
234 return toBN(value).toNumber();
235};
236
237/**
238 * Converts value to it's decimal representation in string
239 *
240 * @method hexToNumberString
241 * @param {String|Number|BN} value
242 * @return {String}
243 */
244var hexToNumberString = function (value) {
245 if (!value) return value;
246
247 return toBN(value).toString(10);
248};
249
250
251/**
252 * Converts value to it's hex representation
253 *
254 * @method numberToHex
255 * @param {String|Number|BN} value
256 * @return {String}
257 */
258var numberToHex = function (value) {
259 if (_.isNull(value) || _.isUndefined(value)) {
260 return value;
261 }
262
263 if (!isFinite(value) && !isHexStrict(value)) {
264 throw new Error('Given input "'+value+'" is not a number.');
265 }
266
267 var number = toBN(value);
268 var result = number.toString(16);
269
270 return number.lt(new BN(0)) ? '-0x' + result.substr(1) : '0x' + result;
271};
272
273
274/**
275 * Convert a byte array to a hex string
276 *
277 * Note: Implementation from crypto-js
278 *
279 * @method bytesToHex
280 * @param {Array} bytes
281 * @return {String} the hex string
282 */
283var bytesToHex = function(bytes) {
284 for (var hex = [], i = 0; i < bytes.length; i++) {
285 /* jshint ignore:start */
286 hex.push((bytes[i] >>> 4).toString(16));
287 hex.push((bytes[i] & 0xF).toString(16));
288 /* jshint ignore:end */
289 }
290 return '0x'+ hex.join("");
291};
292
293/**
294 * Convert a hex string to a byte array
295 *
296 * Note: Implementation from crypto-js
297 *
298 * @method hexToBytes
299 * @param {string} hex
300 * @return {Array} the byte array
301 */
302var hexToBytes = function(hex) {
303 hex = hex.toString(16);
304
305 if (!isHexStrict(hex)) {
306 throw new Error('Given value "'+ hex +'" is not a valid hex string.');
307 }
308
309 hex = hex.replace(/^0x/i,'');
310
311 for (var bytes = [], c = 0; c < hex.length; c += 2)
312 bytes.push(parseInt(hex.substr(c, 2), 16));
313 return bytes;
314};
315
316/**
317 * Auto converts any given value into it's hex representation.
318 *
319 * And even stringifys objects before.
320 *
321 * @method toHex
322 * @param {String|Number|BN|Object} value
323 * @param {Boolean} returnType
324 * @return {String}
325 */
326var toHex = function (value, returnType) {
327 /*jshint maxcomplexity: false */
328
329 if (isAddress(value)) {
330 return returnType ? 'address' : '0x'+ value.toLowerCase().replace(/^0x/i,'');
331 }
332
333 if (_.isBoolean(value)) {
334 return returnType ? 'bool' : value ? '0x01' : '0x00';
335 }
336
337
338 if (_.isObject(value) && !isBigNumber(value) && !isBN(value)) {
339 return returnType ? 'string' : utf8ToHex(JSON.stringify(value));
340 }
341
342 // if its a negative number, pass it through numberToHex
343 if (_.isString(value)) {
344 if (value.indexOf('-0x') === 0 || value.indexOf('-0X') === 0) {
345 return returnType ? 'int256' : numberToHex(value);
346 } else if(value.indexOf('0x') === 0 || value.indexOf('0X') === 0) {
347 return returnType ? 'bytes' : value;
348 } else if (!isFinite(value)) {
349 return returnType ? 'string' : utf8ToHex(value);
350 }
351 }
352
353 return returnType ? (value < 0 ? 'int256' : 'uint256') : numberToHex(value);
354};
355
356
357/**
358 * Check if string is HEX, requires a 0x in front
359 *
360 * @method isHexStrict
361 * @param {String} hex to be checked
362 * @returns {Boolean}
363 */
364var isHexStrict = function (hex) {
365 return ((_.isString(hex) || _.isNumber(hex)) && /^(-)?0x[0-9a-f]*$/i.test(hex));
366};
367
368/**
369 * Check if string is HEX
370 *
371 * @method isHex
372 * @param {String} hex to be checked
373 * @returns {Boolean}
374 */
375var isHex = function (hex) {
376 return ((_.isString(hex) || _.isNumber(hex)) && /^(-0x|0x)?[0-9a-f]*$/i.test(hex));
377};
378
379
380/**
381 * Returns true if given string is a valid Ethereum block header bloom.
382 *
383 * TODO UNDOCUMENTED
384 *
385 * @method isBloom
386 * @param {String} hex encoded bloom filter
387 * @return {Boolean}
388 */
389var isBloom = function (bloom) {
390 if (!/^(0x)?[0-9a-f]{512}$/i.test(bloom)) {
391 return false;
392 } else if (/^(0x)?[0-9a-f]{512}$/.test(bloom) || /^(0x)?[0-9A-F]{512}$/.test(bloom)) {
393 return true;
394 }
395 return false;
396};
397
398/**
399 * Returns true if given string is a valid log topic.
400 *
401 * TODO UNDOCUMENTED
402 *
403 * @method isTopic
404 * @param {String} hex encoded topic
405 * @return {Boolean}
406 */
407var isTopic = function (topic) {
408 if (!/^(0x)?[0-9a-f]{64}$/i.test(topic)) {
409 return false;
410 } else if (/^(0x)?[0-9a-f]{64}$/.test(topic) || /^(0x)?[0-9A-F]{64}$/.test(topic)) {
411 return true;
412 }
413 return false;
414};
415
416
417/**
418 * Hashes values to a sha3 hash using keccak 256
419 *
420 * To hash a HEX string the hex must have 0x in front.
421 *
422 * @method sha3
423 * @return {String} the sha3 string
424 */
425var SHA3_NULL_S = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470';
426
427var sha3 = function (value) {
428 if (isBN(value)) {
429 value = value.toString();
430 }
431
432 if (isHexStrict(value) && /^0x/i.test((value).toString())) {
433 value = hexToBytes(value);
434 }
435
436 var returnValue = Hash.keccak256(value); // jshint ignore:line
437
438 if(returnValue === SHA3_NULL_S) {
439 return null;
440 } else {
441 return returnValue;
442 }
443};
444// expose the under the hood keccak256
445sha3._Hash = Hash;
446
447
448module.exports = {
449 BN: BN,
450 isBN: isBN,
451 isBigNumber: isBigNumber,
452 toBN: toBN,
453 isAddress: isAddress,
454 isBloom: isBloom, // TODO UNDOCUMENTED
455 isTopic: isTopic, // TODO UNDOCUMENTED
456 checkAddressChecksum: checkAddressChecksum,
457 utf8ToHex: utf8ToHex,
458 hexToUtf8: hexToUtf8,
459 hexToNumber: hexToNumber,
460 hexToNumberString: hexToNumberString,
461 numberToHex: numberToHex,
462 toHex: toHex,
463 hexToBytes: hexToBytes,
464 bytesToHex: bytesToHex,
465 isHex: isHex,
466 isHexStrict: isHexStrict,
467 leftPad: leftPad,
468 rightPad: rightPad,
469 toTwosComplement: toTwosComplement,
470 sha3: sha3
471};