/*! * crypto.js - crypto for bcoin * Copyright (c) 2014-2015, Fedor Indutny (MIT License) * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ 'use strict'; var backend = require('./backend'); var native = require('../utils/native').binding; var scrypt = require('./scrypt'); var scryptAsync = require('./scrypt-async'); var crypto = exports; /** * Hash with chosen algorithm. * @function * @param {String} alg * @param {Buffer} data * @returns {Buffer} */ crypto.hash = backend.hash; /** * Hash with chosen algorithm (async). * @function * @param {String} alg * @param {Buffer} data * @returns {Buffer} */ crypto.hashAsync = backend.hashAsync; /** * Hash with ripemd160. * @function * @param {Buffer} data * @returns {Buffer} */ crypto.ripemd160 = backend.ripemd160; /** * Hash with sha1. * @function * @param {Buffer} data * @returns {Buffer} */ crypto.sha1 = backend.sha1; /** * Hash with sha256. * @function * @param {Buffer} data * @returns {Buffer} */ crypto.sha256 = backend.sha256; /** * Hash with sha256 and ripemd160 (OP_HASH160). * @function * @param {Buffer} data * @returns {Buffer} */ crypto.hash160 = backend.hash160; /** * Hash with sha256 twice (OP_HASH256). * @function * @param {Buffer} data * @returns {Buffer} */ crypto.hash256 = backend.hash256; /** * Hash with sha256 twice (async). * @function * @param {Buffer} data * @returns {Buffer} */ crypto.hash256Async = backend.hash256Async; /** * Create an HMAC. * @function * @param {String} alg * @param {Buffer} data * @param {Buffer} key * @returns {Buffer} HMAC */ crypto.hmac = backend.hmac; /** * Create an HMAC (async). * @function * @param {String} alg * @param {Buffer} data * @param {Buffer} key * @returns {Buffer} HMAC */ crypto.hmacAsync = backend.hmacAsync; /** * Perform key derivation using PBKDF2. * @function * @param {Buffer} key * @param {Buffer} salt * @param {Number} iter * @param {Number} len * @param {String} alg * @returns {Buffer} */ crypto.pbkdf2 = backend.pbkdf2; /** * Execute pbkdf2 asynchronously. * @function * @param {Buffer} key * @param {Buffer} salt * @param {Number} iter * @param {Number} len * @param {String} alg * @returns {Promise} */ crypto.pbkdf2Async = backend.pbkdf2Async; /** * Perform key derivation using scrypt. * @function * @param {Buffer} passwd * @param {Buffer} salt * @param {Number} N * @param {Number} r * @param {Number} p * @param {Number} len * @returns {Buffer} */ crypto.scrypt = scrypt; /** * Execute scrypt asynchronously. * @function * @param {Buffer} passwd * @param {Buffer} salt * @param {Number} N * @param {Number} r * @param {Number} p * @param {Number} len * @returns {Promise} */ crypto.scryptAsync = scryptAsync; /** * Perform hkdf extraction. * @param {Buffer} ikm * @param {Buffer} key * @param {String} alg * @returns {Buffer} */ crypto.hkdfExtract = function hkdfExtract(ikm, key, alg) { return crypto.hmac(alg, ikm, key); }; /** * Perform hkdf expansion. * @param {Buffer} prk * @param {Buffer} info * @param {Number} len * @param {String} alg * @returns {Buffer} */ crypto.hkdfExpand = function hkdfExpand(prk, info, len, alg) { var size = crypto.hash(alg, new Buffer(0)).length; var blocks = Math.ceil(len / size); var i, okm, buf, out; if (blocks > 255) throw new Error('Too many blocks.'); okm = new Buffer(len); if (blocks === 0) return okm; buf = new Buffer(size + info.length + 1); // First round: info.copy(buf, size); buf[buf.length - 1] = 1; out = crypto.hmac(alg, buf.slice(size), prk); out.copy(okm, 0); for (i = 1; i < blocks; i++) { out.copy(buf, 0); buf[buf.length - 1]++; out = crypto.hmac(alg, buf, prk); out.copy(okm, i * size); } return okm; }; /** * Build a merkle tree from leaves. * @param {Buffer[]} leaves * @returns {Buffer[]} Tree (in rare cases this may return null). */ crypto.buildMerkleTree = function buildMerkleTree(leaves) { var tree = leaves.slice(); var size = leaves.length; var i, j, i2, hash, left, right, buf; if (size > 1) buf = new Buffer(64); for (j = 0; size > 1; size = ((size + 1) / 2) | 0) { for (i = 0; i < size; i += 2) { i2 = Math.min(i + 1, size - 1); left = tree[j + i]; right = tree[j + i2]; if (i2 === i + 1 && i2 + 1 === size && left.compare(right) === 0) { return; } left.copy(buf, 0); right.copy(buf, 32); hash = crypto.hash256(buf); tree.push(hash); } j += size; } if (tree.length === 0) return; return tree; }; if (native) crypto.buildMerkleTree = native.buildMerkleTree; /** * Calculate merkle root from leaves. * @param {Buffer[]} leaves * @returns {Buffer?} Merkle root. */ crypto.getMerkleRoot = function getMerkleRoot(leaves) { var tree = crypto.buildMerkleTree(leaves); if (!tree) return; return tree[tree.length - 1]; }; /** * Collect a merkle branch at vector index. * @param {Number} index * @param {Buffer[]} leaves * @returns {Buffer[]} branch */ crypto.getMerkleBranch = function getMerkleBranch(index, leaves) { var tree = crypto.buildMerkleTree(leaves); var size = leaves.length; var branch = []; var j = 0; var i; for (; size > 1; size = (size + 1) / 2 | 0) { i = Math.min(index ^ 1, size - 1); branch.push(tree[j + i]); index >>>= 1; j += size; } return branch; }; /** * Check a merkle branch at vector index. * @param {Buffer} hash * @param {Buffer[]} branch * @param {Number} index * @returns {Buffer} Hash. */ crypto.checkMerkleBranch = function checkMerkleBranch(hash, branch, index) { var i, otherside, buf; if (branch.length === 0) return hash; buf = new Buffer(64); for (i = 0; i < branch.length; i++) { otherside = branch[i]; if (index & 1) { otherside.copy(buf, 0); hash.copy(buf, 32); } else { hash.copy(buf, 0); otherside.copy(buf, 32); } hash = crypto.hash256(buf); index >>>= 1; } return hash; }; if (native) crypto.checkMerkleBranch = native.checkMerkleBranch; /** * Encrypt with aes-256-cbc. * @function * @param {Buffer} data * @param {Buffer} key - 256 bit key. * @param {Buffer} iv - 128 bit initialization vector. * @returns {Buffer} */ crypto.encipher = backend.encipher; /** * Encrypt with aes-256-cbc (async). * @function * @param {Buffer} data * @param {Buffer} key - 256 bit key. * @param {Buffer} iv - 128 bit initialization vector. * @returns {Promise} */ crypto.encipherAsync = backend.encipherAsync; /** * Decrypt with aes-256-cbc. * @function * @param {Buffer} data * @param {Buffer} key - 256 bit key. * @param {Buffer} iv - 128 bit initialization vector. * @returns {Buffer} */ crypto.decipher = backend.decipher; /** * Decrypt with aes-256-cbc (async). * @function * @param {Buffer} data * @param {Buffer} key - 256 bit key. * @param {Buffer} iv - 128 bit initialization vector. * @returns {Promise} */ crypto.decipherAsync = backend.decipherAsync; /** * memcmp in constant time (can only return true or false). * This protects us against timing attacks when * comparing an input against a secret string. * @see https://cryptocoding.net/index.php/Coding_rules * @see `$ man 3 memcmp` (NetBSD's consttime_memequal) * @param {Buffer} a * @param {Buffer} b * @returns {Boolean} */ crypto.ccmp = function ccmp(a, b) { var i, res; if (!Buffer.isBuffer(a)) return false; if (!Buffer.isBuffer(b)) return false; if (b.length === 0) return a.length === 0; res = a.length ^ b.length; for (i = 0; i < a.length; i++) res |= a[i] ^ b[i % b.length]; return res === 0; }; /** * A maybe-secure memzero. * @param {Buffer} data */ crypto.cleanse = function cleanse(data) { var ctr = crypto._counter; var i; for (i = 0; i < data.length; i++) { data[i] = ctr & 0xff; ctr += i; } crypto._counter = ctr >>> 0; }; crypto._counter = 0; if (native) crypto.cleanse = native.cleanse; /** * Generate some random bytes. * @function * @param {Number} size * @returns {Buffer} */ crypto.randomBytes = backend.randomBytes; /** * Generate a random uint32. * Probably more cryptographically sound than * `Math.random()`. * @function * @returns {Number} */ crypto.randomInt = function randomInt() { return crypto.randomBytes(4).readUInt32LE(0, true); }; /** * Generate a random number within a range. * Probably more cryptographically sound than * `Math.random()`. * @function * @param {Number} min - Inclusive. * @param {Number} max - Exclusive. * @returns {Number} */ crypto.randomRange = function randomRange(min, max) { var num = crypto.randomInt(); return Math.floor((num / 0x100000000) * (max - min) + min); };