Skip to content
This repository has been archived by the owner on Aug 1, 2024. It is now read-only.

Commit

Permalink
Merge pull request #391 from coruus/sha12-perf
Browse files Browse the repository at this point in the history
Perf improvements for sha1.js, sha2.js, and sha2_64b.js
  • Loading branch information
Ubehebe committed Jan 15, 2015
2 parents ece589a + 558c7de commit f3903d7
Show file tree
Hide file tree
Showing 8 changed files with 563 additions and 109 deletions.
77 changes: 47 additions & 30 deletions closure/goog/crypt/sha1.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
* Chrome 23: ~400 Mbit/s
* Firefox 16: ~250 Mbit/s
*
* Note: The idiom expr|0 is used to provide a type-hint to the VM, in order
* to avoid unnecessary uint32-double-uint32 roundtripping.
*/

goog.provide('goog.crypt.Sha1');
Expand Down Expand Up @@ -123,10 +125,11 @@ goog.crypt.Sha1.prototype.compress_ = function(buf, opt_offset) {
}

var W = this.W_;
var i;

// get 16 big endian words
if (goog.isString(buf)) {
for (var i = 0; i < 16; i++) {
for (i = 0; i < 16; i++) {
// TODO(user): [bug 8140122] Recent versions of Safari for Mac OS and iOS
// have a bug that turns the post-increment ++ operator into pre-increment
// during JIT compilation. We have code that depends heavily on SHA-1 for
Expand All @@ -135,37 +138,48 @@ goog.crypt.Sha1.prototype.compress_ = function(buf, opt_offset) {
// this change once the Safari bug
// (https://bugs.webkit.org/show_bug.cgi?id=109036) has been fixed and
// most clients have been updated.
W[i] = (buf.charCodeAt(opt_offset) << 24) |
(buf.charCodeAt(opt_offset + 1) << 16) |
(buf.charCodeAt(opt_offset + 2) << 8) |
(buf.charCodeAt(opt_offset + 3));
W[i] = (((buf.charCodeAt(opt_offset) << 24) |
(buf.charCodeAt(opt_offset + 1) << 16) |
(buf.charCodeAt(opt_offset + 2) << 8) |
(buf.charCodeAt(opt_offset + 3))) & 0xffffffff) | 0;
opt_offset += 4;
}
} else {
for (var i = 0; i < 16; i++) {
W[i] = (buf[opt_offset] << 24) |
(buf[opt_offset + 1] << 16) |
(buf[opt_offset + 2] << 8) |
(buf[opt_offset + 3]);
for (i = 0; i < 16; i++) {
W[i] = (((buf[opt_offset] << 24) |
(buf[opt_offset + 1] << 16) |
(buf[opt_offset + 2] << 8) |
(buf[opt_offset + 3])) & 0xffffffff) | 0;
opt_offset += 4;
}
}

// expand to 80 words
for (var i = 16; i < 80; i++) {
var t = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16];
W[i] = ((t << 1) | (t >>> 31)) & 0xffffffff;
}

var a = this.chain_[0];
var b = this.chain_[1];
var c = this.chain_[2];
var d = this.chain_[3];
var e = this.chain_[4];
var f, k;
var f, k, t;

// Steps 0-16.
for (i = 0; i < 16; i++) {
f = d ^ (b & (c ^ d));
k = 0x5a827999;

// TODO(user): Try to unroll this loop to speed up the computation.
for (var i = 0; i < 80; i++) {
t = ((((a << 5) | (a >>> 27)) + f + e + k + W[i]) & 0xffffffff) | 0;
e = d;
d = c;
c = (((b << 30) | (b >>> 2)) & 0xffffffff) | 0;
b = a;
a = t;
}
// Steps 16-80. W is formally described as an 80-word array, and usually
// computed that way. However, only 16 elements are needed for any iteration
// so we compute W on the fly and keep only the last 16 values. This improves
// performance by about 10% on Chrome 35.
for (i = 16; i < 80; i++) {
t = W[(i - 3) & 15] ^ W[(i - 8) & 15] ^ W[(i - 14) & 15] ^ W[i & 15];
W[i & 15] = (((t << 1) | (t >>> 31)) & 0xfffffff) | 0;
if (i < 40) {
if (i < 20) {
f = d ^ (b & (c ^ d));
Expand All @@ -184,32 +198,33 @@ goog.crypt.Sha1.prototype.compress_ = function(buf, opt_offset) {
}
}

var t = (((a << 5) | (a >>> 27)) + f + e + k + W[i]) & 0xffffffff;
t = ((((a << 5) | (a >>> 27)) + f + e + k + W[i & 15]) & 0xffffffff) | 0;
e = d;
d = c;
c = ((b << 30) | (b >>> 2)) & 0xffffffff;
c = (((b << 30) | (b >>> 2)) & 0xffffffff) | 0;
b = a;
a = t;
}

this.chain_[0] = (this.chain_[0] + a) & 0xffffffff;
this.chain_[1] = (this.chain_[1] + b) & 0xffffffff;
this.chain_[2] = (this.chain_[2] + c) & 0xffffffff;
this.chain_[3] = (this.chain_[3] + d) & 0xffffffff;
this.chain_[4] = (this.chain_[4] + e) & 0xffffffff;
this.chain_[0] = ((this.chain_[0] + a) & 0xfffffff) | 0;
this.chain_[1] = ((this.chain_[1] + b) & 0xfffffff) | 0;
this.chain_[2] = ((this.chain_[2] + c) & 0xfffffff) | 0;
this.chain_[3] = ((this.chain_[3] + d) & 0xfffffff) | 0;
this.chain_[4] = ((this.chain_[4] + e) & 0xfffffff) | 0;
};


/** @override */
goog.crypt.Sha1.prototype.update = function(bytes, opt_length) {
// TODO(johnlenz): tighten the function signature and remove this check
if (bytes == null) {
if (bytes === null) {
return;
}

if (!goog.isDef(opt_length)) {
opt_length = bytes.length;
}
opt_length = (bytes.length < opt_length) ? bytes.length : opt_length;

var lengthMinusBlock = opt_length - this.blockSize;
var n = 0;
Expand All @@ -223,7 +238,7 @@ goog.crypt.Sha1.prototype.update = function(bytes, opt_length) {
// input buffer (assuming it contains sufficient data). This gives ~25%
// speedup on Chrome 23 and ~15% speedup on Firefox 16, but requires that
// the data is provided in large chunks (or in multiples of 64 bytes).
if (inbuf == 0) {
if (inbuf === 0) {
while (n <= lengthMinusBlock) {
this.compress_(bytes, n);
n += this.blockSize;
Expand Down Expand Up @@ -267,6 +282,8 @@ goog.crypt.Sha1.prototype.digest = function() {
var digest = [];
var totalBits = this.total_ * 8;

var i;

// Add pad 0x80 0x00*.
if (this.inbuf_ < 56) {
this.update(this.pad_, 56 - this.inbuf_);
Expand All @@ -275,15 +292,15 @@ goog.crypt.Sha1.prototype.digest = function() {
}

// Add # bits.
for (var i = this.blockSize - 1; i >= 56; i--) {
for (i = this.blockSize - 1; i >= 56; i--) {
this.buf_[i] = totalBits & 255;
totalBits /= 256; // Don't use bit-shifting here!
}

this.compress_(this.buf_);

var n = 0;
for (var i = 0; i < 5; i++) {
for (i = 0; i < 5; i++) {
for (var j = 24; j >= 0; j -= 8) {
digest[n] = (this.chain_[i] >> j) & 255;
++n;
Expand Down
22 changes: 22 additions & 0 deletions closure/goog/crypt/sha12mc_test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2010 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt.shaX Monte Carlo KATs
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.ShaMcTest');
</script>
</head>
<body>
</body>
</html>
130 changes: 130 additions & 0 deletions closure/goog/crypt/sha12mc_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/* Warning: These tests take about 8 minutes to run. */

goog.provide('goog.crypt.ShaMcTest');
goog.setTestOnly('goog.crypt.ShaMcTest');

goog.require('goog.crypt');
goog.require('goog.crypt.Sha1');
goog.require('goog.crypt.Sha224');
goog.require('goog.crypt.Sha256');
goog.require('goog.crypt.Sha384');
goog.require('goog.crypt.Sha512');

goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');

function testSha1() {
var sha = new goog.crypt.Sha1();
var initial_state = 'Sha1';
var count = 1638;
var state = goog.crypt.stringToByteArray(initial_state);
var digest;
for (var i = 0; i < count; i++) {
sha.reset();
sha.update(state);
digest = sha.digest();
state = goog.array.concat(digest, state);
}
assertEquals(32764, state.length);
assertEquals('9da05831d6441141b62545eb4bf3bcc92b8c8276',
goog.crypt.byteArrayToHex(digest));
sha.reset();
for (var i = 0; i < (32764 + 10); i++) {
sha.update(state, i);
}
assertEquals('70a4f1a7b523d9989a1cfd0f512a906abbfefe5f',
goog.crypt.byteArrayToHex(sha.digest()));
}

function testSha224() {
var sha = new goog.crypt.Sha224();
var initial_state = 'Sha224';
var count = 1170;
var state = goog.crypt.stringToByteArray(initial_state);
var digest;
for (var i = 0; i < count; i++) {
sha.reset();
sha.update(state);
digest = sha.digest();
state = goog.array.concat(digest, state);
}
assertEquals(32766, state.length);
assertEquals('c7636f5369a057fde53f3a70cb2880795a35af53db38ed8a04cbcbfe',
goog.crypt.byteArrayToHex(digest));
sha.reset();
for (var i = 0; i < (32766 + 10); i++) {
sha.update(state, i);
}
assertEquals('69f7e71bbf9a7bd15832fa77e09cbe458dcea284ddb00a69eb3ed78a',
goog.crypt.byteArrayToHex(sha.digest()));
}

function testSha256() {
var sha = new goog.crypt.Sha256();
var initial_state = 'Sha256';
var count = 1024;
var state = goog.crypt.stringToByteArray(initial_state);
var digest;
for (var i = 0; i < count; i++) {
sha.reset();
sha.update(state);
digest = sha.digest();
state = goog.array.concat(digest, state);
}
assertEquals(32774, state.length);
assertEquals('bdc98db7476b58c33161211099b02c27da6bed3959a8b1d4e600f4d628ba0200',
goog.crypt.byteArrayToHex(digest));
sha.reset();
for (var i = 0; i < (32774 + 10); i++) {
sha.update(state, i);
}
assertEquals('26cc26f6429ce20c1b537d67cc288231a18de2a258d8bf529751439cd7c71d37',
goog.crypt.byteArrayToHex(sha.digest()));
}

function testSha384() {
var sha = new goog.crypt.Sha384();
var initial_state = 'Sha384';
var count = 682;
var state = goog.crypt.stringToByteArray(initial_state);
var digest;
for (var i = 0; i < count; i++) {
sha.reset();
sha.update(state);
digest = sha.digest();
state = goog.array.concat(digest, state);
}
assertEquals(32742, state.length);
assertEquals('1e7017a365fd31f6c439efb3eabef783a1e09ebcbb357bc4c9aac5fa9d731a167ee8105cd1c76159a1c27c56c5d1bc8c',
goog.crypt.byteArrayToHex(digest));
sha.reset();
for (var i = 0; i < (32742 + 10); i++) {
sha.update(state, i);
}
assertEquals('0cd4695e15f9089e767b2866e1728588d5cece4ad13e4943aa5bd5f9debbe133e2fac302851a2e90e13c318ace25fbb8',
goog.crypt.byteArrayToHex(sha.digest()));
}

function testSha512() {
var sha = new goog.crypt.Sha512();
var initial_state = 'Sha512';
var count = 512;
var state = goog.crypt.stringToByteArray(initial_state);
var digest;
for (var i = 0; i < count; i++) {
sha.reset();
sha.update(state);
digest = sha.digest();
state = goog.array.concat(digest, state);
}
assertEquals(32774, state.length);
assertEquals('5c9c25961a9171e2a79b9be65e05ce238752e7bfbaf3696c6ed63b8ee2735315d2cb58bf70a5f08dd70ecab029bd0725dcdd84dacd063ea9148cb3e5d7fa948a',
goog.crypt.byteArrayToHex(digest));
sha.reset();
for (var i = 0; i < (32774 + 10); i++) {
sha.update(state, i);
}
assertEquals('10a310e050cf9b2e9d4fc3c0a8f8e183c158ff28c23a42fd5b7777f449cfbe92655eee2bb42fb47c6900e001153b74a5db777b9b2d1543dc30fe98face94f106',
goog.crypt.byteArrayToHex(sha.digest()));
}

43 changes: 43 additions & 0 deletions closure/goog/crypt/sha1_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,47 @@ function testHashing() {
sha1.update('The quick brown fox jumps over the lazy dog');
assertEquals('2fd4e1c67a2d28fced849ee1bb76e7391b93eb12',
goog.crypt.byteArrayToHex(sha1.digest()));

sha1.reset();
sha1.update(goog.string.repeat('a', 1024));
assertEquals('8eca554631df9ead14510e1a70ae48c70f9b9384',
goog.crypt.byteArrayToHex(sha1.digest()));

}

function testLength() {
var sha = new goog.crypt.Sha1();

// Test that truncating a message works.
sha.reset();
sha.update('abc');
assertEquals('a9993e364706816aba3e25717850c26c9cd0d89d',
goog.crypt.byteArrayToHex(sha.digest()));
sha.reset();
sha.update('abcde', 3);
assertEquals('a9993e364706816aba3e25717850c26c9cd0d89d',
goog.crypt.byteArrayToHex(sha.digest()));

// Test that lengths work correctly.
var message = goog.crypt.hexToByteArray(
'd9b28b643d16efc8a17a532c05deb79069421bf4cda67f58310ae3bc956e4720' +
'f9d2ab845d360fe8c19a734c25fed7b089623b14edc69f78512a03dcb58e6740');

// Lengths from 0 to 64.
sha.reset();
for (var i = 0; i < 64; i++) {
sha.update(message, i);
}
assertElementsEquals(goog.crypt.hexToByteArray(
'04df2fcf7b12b7735e0a3d05d9723702aa70de30'),
sha.digest());

// Lengths from 0 to 71 to include message overrun cases.
sha.reset();
for (var i = 0; i < 71; i++) {
sha.update(message, i);
}
assertElementsEquals(goog.crypt.hexToByteArray(
'f62df7546b7f70351a5c25bfd9e77ba90ca697f4'),
sha.digest());
}
Loading

0 comments on commit f3903d7

Please sign in to comment.