当我在 JavaScript 中实现 ChaCha20 时,遇到了一些奇怪的行为。
我的第一个版本是这样构建的(我们称之为“封装版本”):
function quarterRound(x, a, b, c, d) {
x[a] += x[b]; x[d] = ((x[d] ^ x[a]) << 16) | ((x[d] ^ x[a]) >>> 16);
x[c] += x[d]; x[b] = ((x[b] ^ x[c]) << 12) | ((x[b] ^ x[c]) >>> 20);
x[a] += x[b]; x[d] = ((x[d] ^ x[a]) << 8) | ((x[d] ^ x[a]) >>> 24);
x[c] += x[d]; x[b] = ((x[b] ^ x[c]) << 7) | ((x[b] ^ x[c]) >>> 25);
}
function getBlock(buffer) {
var x = new Uint32Array(16);
for (var i = 16; i--;) x[i] = input[i];
for (var i = 20; i > 0; i -= 2) {
quarterRound(x, 0, 4, 8,12);
quarterRound(x, 1, 5, 9,13);
quarterRound(x, 2, 6,10,14);
quarterRound(x, 3, 7,11,15);
quarterRound(x, 0, 5,10,15);
quarterRound(x, 1, 6,11,12);
quarterRound(x, 2, 7, 8,13);
quarterRound(x, 3, 4, 9,14);
}
for (i = 16; i--;) x[i] += input[i];
for (i = 16; i--;) U32TO8_LE(buffer, 4 * i, x[i]);
input[12]++;
return buffer;
}
为了减少不必要的函数调用(包括参数开销等),我移除了quarterRound
函数并将其内容嵌入到代码中(它是正确的,我已经针对一些测试向量进行了验证):
function getBlock(buffer) {
var x = new Uint32Array(16);
for (var i = 16; i--;) x[i] = input[i];
for (var i = 20; i > 0; i -= 2) {
x[ 0] += x[ 4]; x[12] = ((x[12] ^ x[ 0]) << 16) | ((x[12] ^ x[ 0]) >>> 16);
x[ 8] += x[12]; x[ 4] = ((x[ 4] ^ x[ 8]) << 12) | ((x[ 4] ^ x[ 8]) >>> 20);
x[ 0] += x[ 4]; x[12] = ((x[12] ^ x[ 0]) << 8) | ((x[12] ^ x[ 0]) >>> 24);
x[ 8] += x[12]; x[ 4] = ((x[ 4] ^ x[ 8]) << 7) | ((x[ 4] ^ x[ 8]) >>> 25);
x[ 1] += x[ 5]; x[13] = ((x[13] ^ x[ 1]) << 16) | ((x[13] ^ x[ 1]) >>> 16);
x[ 9] += x[13]; x[ 5] = ((x[ 5] ^ x[ 9]) << 12) | ((x[ 5] ^ x[ 9]) >>> 20);
x[ 1] += x[ 5]; x[13] = ((x[13] ^ x[ 1]) << 8) | ((x[13] ^ x[ 1]) >>> 24);
x[ 9] += x[13]; x[ 5] = ((x[ 5] ^ x[ 9]) << 7) | ((x[ 5] ^ x[ 9]) >>> 25);
x[ 2] += x[ 6]; x[14] = ((x[14] ^ x[ 2]) << 16) | ((x[14] ^ x[ 2]) >>> 16);
x[10] += x[14]; x[ 6] = ((x[ 6] ^ x[10]) << 12) | ((x[ 6] ^ x[10]) >>> 20);
x[ 2] += x[ 6]; x[14] = ((x[14] ^ x[ 2]) << 8) | ((x[14] ^ x[ 2]) >>> 24);
x[10] += x[14]; x[ 6] = ((x[ 6] ^ x[10]) << 7) | ((x[ 6] ^ x[10]) >>> 25);
x[ 3] += x[ 7]; x[15] = ((x[15] ^ x[ 3]) << 16) | ((x[15] ^ x[ 3]) >>> 16);
x[11] += x[15]; x[ 7] = ((x[ 7] ^ x[11]) << 12) | ((x[ 7] ^ x[11]) >>> 20);
x[ 3] += x[ 7]; x[15] = ((x[15] ^ x[ 3]) << 8) | ((x[15] ^ x[ 3]) >>> 24);
x[11] += x[15]; x[ 7] = ((x[ 7] ^ x[11]) << 7) | ((x[ 7] ^ x[11]) >>> 25);
x[ 0] += x[ 5]; x[15] = ((x[15] ^ x[ 0]) << 16) | ((x[15] ^ x[ 0]) >>> 16);
x[10] += x[15]; x[ 5] = ((x[ 5] ^ x[10]) << 12) | ((x[ 5] ^ x[10]) >>> 20);
x[ 0] += x[ 5]; x[15] = ((x[15] ^ x[ 0]) << 8) | ((x[15] ^ x[ 0]) >>> 24);
x[10] += x[15]; x[ 5] = ((x[ 5] ^ x[10]) << 7) | ((x[ 5] ^ x[10]) >>> 25);
x[ 1] += x[ 6]; x[12] = ((x[12] ^ x[ 1]) << 16) | ((x[12] ^ x[ 1]) >>> 16);
x[11] += x[12]; x[ 6] = ((x[ 6] ^ x[11]) << 12) | ((x[ 6] ^ x[11]) >>> 20);
x[ 1] += x[ 6]; x[12] = ((x[12] ^ x[ 1]) << 8) | ((x[12] ^ x[ 1]) >>> 24);
x[11] += x[12]; x[ 6] = ((x[ 6] ^ x[11]) << 7) | ((x[ 6] ^ x[11]) >>> 25);
x[ 2] += x[ 7]; x[13] = ((x[13] ^ x[ 2]) << 16) | ((x[13] ^ x[ 2]) >>> 16);
x[ 8] += x[13]; x[ 7] = ((x[ 7] ^ x[ 8]) << 12) | ((x[ 7] ^ x[ 8]) >>> 20);
x[ 2] += x[ 7]; x[13] = ((x[13] ^ x[ 2]) << 8) | ((x[13] ^ x[ 2]) >>> 24);
x[ 8] += x[13]; x[ 7] = ((x[ 7] ^ x[ 8]) << 7) | ((x[ 7] ^ x[ 8]) >>> 25);
x[ 3] += x[ 4]; x[14] = ((x[14] ^ x[ 3]) << 16) | ((x[14] ^ x[ 3]) >>> 16);
x[ 9] += x[14]; x[ 4] = ((x[ 4] ^ x[ 9]) << 12) | ((x[ 4] ^ x[ 9]) >>> 20);
x[ 3] += x[ 4]; x[14] = ((x[14] ^ x[ 3]) << 8) | ((x[14] ^ x[ 3]) >>> 24);
x[ 9] += x[14]; x[ 4] = ((x[ 4] ^ x[ 9]) << 7) | ((x[ 4] ^ x[ 9]) >>> 25);
}
for (i = 16; i--;) x[i] += input[i];
for (i = 16; i--;) U32TO8_LE(buffer, 4 * i, x[i]);
input[12]++;
return buffer;
}
但是性能结果并不尽如人意:
对比之下
尽管在Firefox和Safari下性能差异微不足道或者不重要,但是在Chrome下性能损失巨大...有任何想法为什么会这样?
P.S.: 如果图片太小,请在新标签页中打开 :)
PP.S.: 这里是链接: