fcoin/lib/crypto/crypto.js
2016-11-21 17:50:05 -08:00

468 lines
8.7 KiB
JavaScript

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