fcoin/lib/crypto/chacha20.js
2017-07-31 18:21:02 -07:00

206 lines
4.3 KiB
JavaScript

/*!
* chacha20.js - chacha20 for bcoin
* Copyright (c) 2016-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
const assert = require('assert');
const native = require('../native').binding;
const BIG_ENDIAN = new Int8Array(new Int16Array([1]).buffer)[0] === 0;
/**
* ChaCha20 (used for bip151)
* @alias module:crypto.ChaCha20
* @constructor
* @see https://tools.ietf.org/html/rfc7539#section-2
*/
function ChaCha20() {
if (!(this instanceof ChaCha20))
return new ChaCha20();
this.state = new Uint32Array(16);
this.stream = new Uint32Array(16);
this.bytes = new Uint8Array(this.stream.buffer);
if (BIG_ENDIAN)
this.bytes = Buffer.allocUnsafe(64);
this.pos = 0;
this.ivSize = 0;
}
/**
* Initialize chacha20 with a key, iv, and counter.
* @param {Buffer} key
* @param {Buffer} iv
* @param {Number} counter
*/
ChaCha20.prototype.init = function init(key, iv, counter) {
if (key)
this.initKey(key);
if (iv)
this.initIV(iv, counter);
};
/**
* Set key.
* @param {Buffer} key
*/
ChaCha20.prototype.initKey = function initKey(key) {
this.state[0] = 0x61707865;
this.state[1] = 0x3320646e;
this.state[2] = 0x79622d32;
this.state[3] = 0x6b206574;
this.state[4] = key.readUInt32LE(0, true);
this.state[5] = key.readUInt32LE(4, true);
this.state[6] = key.readUInt32LE(8, true);
this.state[7] = key.readUInt32LE(12, true);
this.state[8] = key.readUInt32LE(16, true);
this.state[9] = key.readUInt32LE(20, true);
this.state[10] = key.readUInt32LE(24, true);
this.state[11] = key.readUInt32LE(28, true);
this.state[12] = 0;
this.pos = 0xffffffff;
};
/**
* Set IV and counter.
* @param {Buffer} iv
* @param {Number} counter
*/
ChaCha20.prototype.initIV = function initIV(iv, counter) {
if (iv.length === 8) {
this.state[13] = 0;
this.state[14] = iv.readUInt32LE(0, true);
this.state[15] = iv.readUInt32LE(4, true);
} else if (iv.length === 12) {
this.state[13] = iv.readUInt32LE(0, true);
this.state[14] = iv.readUInt32LE(4, true);
this.state[15] = iv.readUInt32LE(8, true);
} else {
assert(false, 'Bad iv size.');
}
this.ivSize = iv.length * 8;
this.setCounter(counter);
};
/**
* Encrypt/decrypt data.
* @param {Buffer} data - Will be mutated.
*/
ChaCha20.prototype.encrypt = function encrypt(data) {
for (let i = 0; i < data.length; i++) {
if (this.pos >= 64) {
for (let j = 0; j < 16; j++)
this.stream[j] = this.state[j];
for (let j = 0; j < 10; j++) {
qround(this.stream, 0, 4, 8, 12);
qround(this.stream, 1, 5, 9, 13);
qround(this.stream, 2, 6, 10, 14);
qround(this.stream, 3, 7, 11, 15);
qround(this.stream, 0, 5, 10, 15);
qround(this.stream, 1, 6, 11, 12);
qround(this.stream, 2, 7, 8, 13);
qround(this.stream, 3, 4, 9, 14);
}
for (let j = 0; j < 16; j++) {
this.stream[j] += this.state[j];
if (BIG_ENDIAN)
this.bytes.writeUInt32LE(this.stream[j], j * 4, true);
}
this.state[12]++;
if (this.state[12] === 0) {
assert(this.ivSize === 64, 'Counter overflow.');
this.state[13]++;
assert(this.state[13] !== 0, 'Counter overflow.');
}
this.pos = 0;
}
data[i] ^= this.bytes[this.pos++];
}
return data;
};
/**
* Artificially set the counter.
* @param {Number} counter
*/
ChaCha20.prototype.setCounter = function setCounter(counter) {
if (!counter)
counter = 0;
const lo = counter % 0x100000000;
const hi = (counter - lo) / 0x100000000;
this.state[12] = lo;
if (this.ivSize === 64)
this.state[13] = hi;
};
/**
* Get the counter as a uint64.
* @returns {Number}
*/
ChaCha20.prototype.getCounter = function getCounter() {
const lo = this.state[12];
const hi = this.state[13];
if (this.ivSize === 64)
return hi * 0x100000000 + lo;
return lo;
};
if (native)
ChaCha20 = native.ChaCha20;
/*
* Helpers
*/
function qround(x, a, b, c, d) {
x[a] += x[b];
x[d] = rotl32(x[d] ^ x[a], 16);
x[c] += x[d];
x[b] = rotl32(x[b] ^ x[c], 12);
x[a] += x[b];
x[d] = rotl32(x[d] ^ x[a], 8);
x[c] += x[d];
x[b] = rotl32(x[b] ^ x[c], 7);
}
function rotl32(w, b) {
return (w << b) | (w >>> (32 - b));
}
/*
* Expose
*/
module.exports = ChaCha20;