From 1089af29772c13ecc3b710d5ff1bf50cc7e17d63 Mon Sep 17 00:00:00 2001 From: Igor Zhukov Date: Thu, 23 Oct 2014 13:33:46 +0400 Subject: [PATCH] Improved SHA-1 performance with Rusha library --- app/index.html | 1 + app/js/lib/bin_utils.js | 7 +- app/js/lib/crypto_worker.js | 3 +- app/js/lib/tl_utils.js | 28 ++- app/vendor/rusha/rusha.js | 392 ++++++++++++++++++++++++++++++++++++ app/webogram.appcache | 2 +- 6 files changed, 417 insertions(+), 16 deletions(-) create mode 100644 app/vendor/rusha/rusha.js diff --git a/app/index.html b/app/index.html index 7d437a38..df7d7019 100644 --- a/app/index.html +++ b/app/index.html @@ -56,6 +56,7 @@ + diff --git a/app/js/lib/bin_utils.js b/app/js/lib/bin_utils.js index 38bbf77f..ecbfdfd4 100644 --- a/app/js/lib/bin_utils.js +++ b/app/js/lib/bin_utils.js @@ -196,9 +196,10 @@ function uintToInt (val) { } function sha1Hash (bytes) { - // console.log('SHA-1 hash start'); - var hashBytes = sha1.hash(bytes, true); - // console.log('SHA-1 hash finish'); + this.rushaInstance = this.rushaInstance || new Rusha(1024 * 1024); + // console.log(dT(), 'SHA-1 hash start', bytes.byteLength || bytes.length); + var hashBytes = bytesFromArrayBuffer(rushaInstance.rawDigest(bytes).buffer); + // console.log(dT(), 'SHA-1 hash finish'); return hashBytes; } diff --git a/app/js/lib/crypto_worker.js b/app/js/lib/crypto_worker.js index 3340a9d6..2f42cf55 100644 --- a/app/js/lib/crypto_worker.js +++ b/app/js/lib/crypto_worker.js @@ -11,7 +11,8 @@ importScripts( '../../vendor/jsbn/jsbn_combined.js', '../../vendor/leemon_bigint/bigint.js', '../../vendor/closure/long.js', - '../../vendor/cryptoJS/crypto.js' + '../../vendor/cryptoJS/crypto.js', + '../../vendor/rusha/rusha.js' ); onmessage = function (e) { diff --git a/app/js/lib/tl_utils.js b/app/js/lib/tl_utils.js index a94f6651..18c736b2 100644 --- a/app/js/lib/tl_utils.js +++ b/app/js/lib/tl_utils.js @@ -36,7 +36,16 @@ TLSerialization.prototype.getBuffer = function () { return this.getArray().buffer; }; -TLSerialization.prototype.getBytes = function () { +TLSerialization.prototype.getBytes = function (typed) { + if (typed) { + var resultBuffer = new ArrayBuffer(this.offset); + var resultArray = new Uint8Array(resultBuffer); + + resultArray.set(this.byteView.subarray(0, this.offset)); + + return resultArray; + } + var bytes = []; for (var i = 0; i < this.offset; i++) { bytes.push(this.byteView[i]); @@ -141,9 +150,8 @@ TLSerialization.prototype.storeString = function (s, field) { TLSerialization.prototype.storeBytes = function (bytes, field) { this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':bytes'); - this.checkLength(bytes.length + 8); - - var len = bytes.length; + var len = bytes.byteLength || bytes.length; + this.checkLength(len + 8); if (len <= 253) { this.byteView[this.offset++] = len; } else { @@ -152,9 +160,8 @@ TLSerialization.prototype.storeBytes = function (bytes, field) { this.byteView[this.offset++] = (len & 0xFF00) >> 8; this.byteView[this.offset++] = (len & 0xFF0000) >> 16; } - for (var i = 0; i < len; i++) { - this.byteView[this.offset++] = bytes[i]; - } + this.byteView.set(bytes, this.offset); + this.offset += len; // Padding while (this.offset % 4) { @@ -177,14 +184,13 @@ TLSerialization.prototype.storeIntBytes = function (bytes, bits, field) { }; TLSerialization.prototype.storeRawBytes = function (bytes, field) { - var len = bytes.length; + var len = bytes.byteLength || bytes.length; this.debug && console.log('>>>', bytesToHex(bytes), (field || '')); this.checkLength(len); - for (var i = 0; i < len; i++) { - this.byteView[this.offset++] = bytes[i]; - } + this.byteView.set(bytes, this.offset); + this.offset += len; }; diff --git a/app/vendor/rusha/rusha.js b/app/vendor/rusha/rusha.js new file mode 100644 index 00000000..dd550de0 --- /dev/null +++ b/app/vendor/rusha/rusha.js @@ -0,0 +1,392 @@ +/* + * Rusha, a JavaScript implementation of the Secure Hash Algorithm, SHA-1, + * as defined in FIPS PUB 180-1, tuned for high performance with large inputs. + * (http://github.com/srijs/rusha) + * + * Inspired by Paul Johnstons implementation (http://pajhome.org.uk/crypt/md5). + * + * Copyright (c) 2013 Sam Rijs (http://awesam.de). + * Released under the terms of the MIT license as follows: + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +(function (global) { + // If we'e running in Node.JS, export a module. + if (typeof module !== 'undefined') { + module.exports = Rusha; + } + // If we're running in a DOM context, export + // the Rusha object to toplevel. + if (typeof global !== 'undefined') { + global.Rusha = Rusha; + } + // If we're running in a webworker, accept + // messages containing a jobid and a buffer + // or blob object, and return the hash result. + if (typeof FileReaderSync !== 'undefined') { + var reader = new FileReaderSync(), hasher = new Rusha(4 * 1024 * 1024); + self.onmessage = function onMessage(event) { + var hash, data = event.data.data; + if (data instanceof Blob) { + try { + data = reader.readAsBinaryString(data); + } catch (e) { + self.postMessage({ + id: event.data.id, + error: e.name + }); + return; + } + } + hash = hasher.digest(data); + self.postMessage({ + id: event.data.id, + hash: hash + }); + }; + } + var util = { + getDataType: function (data) { + if (typeof data === 'string') { + return 'string'; + } + if (data instanceof Array) { + return 'array'; + } + if (typeof global !== 'undefined' && global.Buffer && global.Buffer.isBuffer(data)) { + return 'buffer'; + } + if (data instanceof ArrayBuffer) { + return 'arraybuffer'; + } + if (data.buffer instanceof ArrayBuffer) { + return 'view'; + } + throw new Error('Unsupported data type.'); + } + }; + // The Rusha object is a wrapper around the low-level RushaCore. + // It provides means of converting different inputs to the + // format accepted by RushaCore as well as other utility methods. + function Rusha(chunkSize) { + 'use strict'; + // Private object structure. + var self$2 = { fill: 0 }; + // Calculate the length of buffer that the sha1 routine uses + // including the padding. + var padlen = function (len) { + for (len += 9; len % 64 > 0; len += 1); + return len; + }; + var padZeroes = function (bin, len) { + for (var i = len >> 2; i < bin.length; i++) + bin[i] = 0; + }; + var padData = function (bin, chunkLen, msgLen) { + bin[chunkLen >> 2] |= 128 << 24 - (chunkLen % 4 << 3); + bin[((chunkLen >> 2) + 2 & ~15) + 15] = msgLen << 3; + }; + // Convert a binary string and write it to the heap. + // A binary string is expected to only contain char codes < 256. + var convStr = function (H8, H32, start, len, off) { + var str = this, i, om = off % 4, lm = len % 4, j = len - lm; + if (j > 0) { + switch (om) { + case 0: + H8[off + 3 | 0] = str.charCodeAt(start); + case 1: + H8[off + 2 | 0] = str.charCodeAt(start + 1); + case 2: + H8[off + 1 | 0] = str.charCodeAt(start + 2); + case 3: + H8[off | 0] = str.charCodeAt(start + 3); + } + } + for (i = om; i < j; i = i + 4 | 0) { + H32[off + i >> 2] = str.charCodeAt(start + i) << 24 | str.charCodeAt(start + i + 1) << 16 | str.charCodeAt(start + i + 2) << 8 | str.charCodeAt(start + i + 3); + } + switch (lm) { + case 3: + H8[off + j + 1 | 0] = str.charCodeAt(start + j + 2); + case 2: + H8[off + j + 2 | 0] = str.charCodeAt(start + j + 1); + case 1: + H8[off + j + 3 | 0] = str.charCodeAt(start + j); + } + }; + // Convert a buffer or array and write it to the heap. + // The buffer or array is expected to only contain elements < 256. + var convBuf = function (H8, H32, start, len, off) { + var buf = this, i, om = off % 4, lm = len % 4, j = len - lm; + if (j > 0) { + switch (om) { + case 0: + H8[off + 3 | 0] = buf[start]; + case 1: + H8[off + 2 | 0] = buf[start + 1]; + case 2: + H8[off + 1 | 0] = buf[start + 2]; + case 3: + H8[off | 0] = buf[start + 3]; + } + } + for (i = 4 - om; i < j; i = i += 4 | 0) { + H32[off + i >> 2] = buf[start + i] << 24 | buf[start + i + 1] << 16 | buf[start + i + 2] << 8 | buf[start + i + 3]; + } + switch (lm) { + case 3: + H8[off + j + 1 | 0] = buf[start + j + 2]; + case 2: + H8[off + j + 2 | 0] = buf[start + j + 1]; + case 1: + H8[off + j + 3 | 0] = buf[start + j]; + } + }; + var convFn = function (data) { + switch (util.getDataType(data)) { + case 'string': + return convStr.bind(data); + case 'array': + return convBuf.bind(data); + case 'buffer': + return convBuf.bind(data); + case 'arraybuffer': + return convBuf.bind(new Uint8Array(data)); + case 'view': + return convBuf.bind(new Uint8Array(data.buffer)); + } + }; + var slice = function (data, offset) { + switch (util.getDataType(data)) { + case 'string': + return data.slice(offset); + case 'array': + return data.slice(offset); + case 'buffer': + return data.slice(offset); + case 'arraybuffer': + return data.slice(offset); + case 'view': + return data.buffer.slice(offset); + } + }; + // Convert an ArrayBuffer into its hexadecimal string representation. + var hex = function (arrayBuffer) { + var i, x, hex_tab = '0123456789abcdef', res = [], binarray = new Uint8Array(arrayBuffer); + for (i = 0; i < binarray.length; i++) { + x = binarray[i]; + res[i] = hex_tab.charAt(x >> 4 & 15) + hex_tab.charAt(x >> 0 & 15); + } + return res.join(''); + }; + var ceilHeapSize = function (v) { + // The asm.js spec says: + // The heap object's byteLength must be either + // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1. + // Also, byteLengths smaller than 2^16 are deprecated. + var p; + // If v is smaller than 2^16, the smallest possible solution + // is 2^16. + if (v <= 65536) + return 65536; + // If v < 2^24, we round up to 2^n, + // otherwise we round up to 2^24 * n. + if (v < 16777216) { + for (p = 1; p < v; p = p << 1); + } else { + for (p = 16777216; p < v; p += 16777216); + } + return p; + }; + // Initialize the internal data structures to a new capacity. + var init = function (size) { + if (size % 64 > 0) { + throw new Error('Chunk size must be a multiple of 128 bit'); + } + self$2.maxChunkLen = size; + self$2.padMaxChunkLen = padlen(size); + // The size of the heap is the sum of: + // 1. The padded input message size + // 2. The extended space the algorithm needs (320 byte) + // 3. The 160 bit state the algoritm uses + self$2.heap = new ArrayBuffer(ceilHeapSize(self$2.padMaxChunkLen + 320 + 20)); + self$2.h32 = new Int32Array(self$2.heap); + self$2.h8 = new Int8Array(self$2.heap); + self$2.core = RushaCore({ + Int32Array: Int32Array, + DataView: DataView + }, {}, self$2.heap); + self$2.buffer = null; + }; + // Iinitializethe datastructures according + // to a chunk siyze. + init(chunkSize || 64 * 1024); + var initState = function (heap, padMsgLen) { + var io = new Int32Array(heap, padMsgLen + 320, 5); + io[0] = 1732584193; + io[1] = -271733879; + io[2] = -1732584194; + io[3] = 271733878; + io[4] = -1009589776; + }; + var padChunk = function (chunkLen, msgLen) { + var padChunkLen = padlen(chunkLen); + var view = new Int32Array(self$2.heap, 0, padChunkLen >> 2); + padZeroes(view, chunkLen); + padData(view, chunkLen, msgLen); + return padChunkLen; + }; + // Write data to the heap. + var write = function (data, chunkOffset, chunkLen) { + convFn(data)(self$2.h8, self$2.h32, chunkOffset, chunkLen, 0); + }; + // Initialize and call the RushaCore, + // assuming an input buffer of length len * 4. + var coreCall = function (data, chunkOffset, chunkLen, msgLen, finalize) { + var padChunkLen = chunkLen; + if (finalize) { + padChunkLen = padChunk(chunkLen, msgLen); + } + write(data, chunkOffset, chunkLen); + self$2.core.hash(padChunkLen, self$2.padMaxChunkLen); + }; + var getRawDigest = function (heap, padMaxChunkLen) { + var io = new Int32Array(heap, padMaxChunkLen + 320, 5); + var out = new Int32Array(5); + var arr = new DataView(out.buffer); + arr.setInt32(0, io[0], false); + arr.setInt32(4, io[1], false); + arr.setInt32(8, io[2], false); + arr.setInt32(12, io[3], false); + arr.setInt32(16, io[4], false); + return out; + }; + // Calculate the hash digest as an array of 5 32bit integers. + var rawDigest = this.rawDigest = function (str) { + var msgLen = str.byteLength || str.length; + initState(self$2.heap, self$2.padMaxChunkLen); + var chunkOffset = 0, chunkLen = self$2.maxChunkLen, last; + for (chunkOffset = 0; msgLen > chunkOffset + chunkLen; chunkOffset += chunkLen) { + coreCall(str, chunkOffset, chunkLen, msgLen, false); + } + coreCall(str, chunkOffset, msgLen - chunkOffset, msgLen, true); + return getRawDigest(self$2.heap, self$2.padMaxChunkLen); + }; + // The digest and digestFrom* interface returns the hash digest + // as a hex string. + this.digest = this.digestFromString = this.digestFromBuffer = this.digestFromArrayBuffer = function (str) { + return hex(rawDigest(str).buffer); + }; + } + ; + // The low-level RushCore module provides the heart of Rusha, + // a high-speed sha1 implementation working on an Int32Array heap. + // At first glance, the implementation seems complicated, however + // with the SHA1 spec at hand, it is obvious this almost a textbook + // implementation that has a few functions hand-inlined and a few loops + // hand-unrolled. + function RushaCore(stdlib, foreign, heap) { + 'use asm'; + var H = new stdlib.Int32Array(heap); + function hash(k, x) { + // k in bytes + k = k | 0; + x = x | 0; + var i = 0, j = 0, y0 = 0, z0 = 0, y1 = 0, z1 = 0, y2 = 0, z2 = 0, y3 = 0, z3 = 0, y4 = 0, z4 = 0, t0 = 0, t1 = 0; + y0 = H[x + 320 >> 2] | 0; + y1 = H[x + 324 >> 2] | 0; + y2 = H[x + 328 >> 2] | 0; + y3 = H[x + 332 >> 2] | 0; + y4 = H[x + 336 >> 2] | 0; + for (i = 0; (i | 0) < (k | 0); i = i + 64 | 0) { + z0 = y0; + z1 = y1; + z2 = y2; + z3 = y3; + z4 = y4; + for (j = 0; (j | 0) < 64; j = j + 4 | 0) { + t1 = H[i + j >> 2] | 0; + t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | ~y1 & y3) | 0) + ((t1 + y4 | 0) + 1518500249 | 0) | 0; + y4 = y3; + y3 = y2; + y2 = y1 << 30 | y1 >>> 2; + y1 = y0; + y0 = t0; + ; + H[k + j >> 2] = t1; + } + for (j = k + 64 | 0; (j | 0) < (k + 80 | 0); j = j + 4 | 0) { + t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31; + t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | ~y1 & y3) | 0) + ((t1 + y4 | 0) + 1518500249 | 0) | 0; + y4 = y3; + y3 = y2; + y2 = y1 << 30 | y1 >>> 2; + y1 = y0; + y0 = t0; + ; + H[j >> 2] = t1; + } + for (j = k + 80 | 0; (j | 0) < (k + 160 | 0); j = j + 4 | 0) { + t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31; + t0 = ((y0 << 5 | y0 >>> 27) + (y1 ^ y2 ^ y3) | 0) + ((t1 + y4 | 0) + 1859775393 | 0) | 0; + y4 = y3; + y3 = y2; + y2 = y1 << 30 | y1 >>> 2; + y1 = y0; + y0 = t0; + ; + H[j >> 2] = t1; + } + for (j = k + 160 | 0; (j | 0) < (k + 240 | 0); j = j + 4 | 0) { + t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31; + t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | y1 & y3 | y2 & y3) | 0) + ((t1 + y4 | 0) - 1894007588 | 0) | 0; + y4 = y3; + y3 = y2; + y2 = y1 << 30 | y1 >>> 2; + y1 = y0; + y0 = t0; + ; + H[j >> 2] = t1; + } + for (j = k + 240 | 0; (j | 0) < (k + 320 | 0); j = j + 4 | 0) { + t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31; + t0 = ((y0 << 5 | y0 >>> 27) + (y1 ^ y2 ^ y3) | 0) + ((t1 + y4 | 0) - 899497514 | 0) | 0; + y4 = y3; + y3 = y2; + y2 = y1 << 30 | y1 >>> 2; + y1 = y0; + y0 = t0; + ; + H[j >> 2] = t1; + } + y0 = y0 + z0 | 0; + y1 = y1 + z1 | 0; + y2 = y2 + z2 | 0; + y3 = y3 + z3 | 0; + y4 = y4 + z4 | 0; + } + H[x + 320 >> 2] = y0; + H[x + 324 >> 2] = y1; + H[x + 328 >> 2] = y2; + H[x + 332 >> 2] = y3; + H[x + 336 >> 2] = y4; + } + return { hash: hash }; + } +}(this)); diff --git a/app/webogram.appcache b/app/webogram.appcache index f32aa44a..8cf21886 100644 --- a/app/webogram.appcache +++ b/app/webogram.appcache @@ -1,6 +1,6 @@ CACHE MANIFEST -# 39 +# 40 NETWORK: *