/*! * 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;