675 lines
14 KiB
JavaScript
675 lines
14 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 assert = require('assert');
|
|
var random = require('./random');
|
|
var scrypt = require('./scrypt');
|
|
var scryptAsync = require('./scrypt-async');
|
|
var utils = require('../utils/utils');
|
|
var co = require('../utils/co');
|
|
var native = require('../utils/native');
|
|
var lazy = require('../utils/lazy')(require, exports);
|
|
var nodeCrypto, hash, aes;
|
|
|
|
var isBrowser =
|
|
(typeof process !== 'undefined' && process.browser)
|
|
|| typeof window !== 'undefined';
|
|
|
|
if (!isBrowser) {
|
|
nodeCrypto = require('crypto');
|
|
} else {
|
|
hash = require('hash.js');
|
|
aes = require('./aes');
|
|
}
|
|
|
|
/**
|
|
* @exports crypto
|
|
*/
|
|
|
|
var crypto = exports;
|
|
|
|
/**
|
|
* Hash with chosen algorithm.
|
|
* @param {String} alg
|
|
* @param {Buffer} data
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.hash = function _hash(alg, data) {
|
|
if (!nodeCrypto)
|
|
return new Buffer(hash[alg]().update(data).digest());
|
|
|
|
return nodeCrypto.createHash(alg).update(data).digest();
|
|
};
|
|
|
|
if (native)
|
|
crypto.hash = native.hash;
|
|
|
|
/**
|
|
* Hash with ripemd160.
|
|
* @param {Buffer} data
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.ripemd160 = function ripemd160(data) {
|
|
return crypto.hash('ripemd160', data);
|
|
};
|
|
|
|
/**
|
|
* Hash with sha1.
|
|
* @param {Buffer} data
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.sha1 = function sha1(data) {
|
|
return crypto.hash('sha1', data);
|
|
};
|
|
|
|
/**
|
|
* Hash with sha256.
|
|
* @param {Buffer} data
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.sha256 = function sha256(data) {
|
|
return crypto.hash('sha256', data);
|
|
};
|
|
|
|
if (native)
|
|
crypto.sha256 = native.sha256;
|
|
|
|
/**
|
|
* Hash with sha256 and ripemd160 (OP_HASH160).
|
|
* @param {Buffer} data
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.hash160 = function hash160(data) {
|
|
return crypto.ripemd160(crypto.sha256(data));
|
|
};
|
|
|
|
if (native)
|
|
crypto.hash160 = native.hash160;
|
|
|
|
/**
|
|
* Hash with sha256 twice (OP_HASH256).
|
|
* @param {Buffer} data
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.hash256 = function hash256(data) {
|
|
return crypto.sha256(crypto.sha256(data));
|
|
};
|
|
|
|
if (native)
|
|
crypto.hash256 = native.hash256;
|
|
|
|
/**
|
|
* Create a sha256 checksum (common in bitcoin).
|
|
* @param {Buffer} data
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.checksum = function checksum(data) {
|
|
return crypto.hash256(data).slice(0, 4);
|
|
};
|
|
|
|
/**
|
|
* Create an HMAC.
|
|
* @param {String} alg
|
|
* @param {Buffer} data
|
|
* @param {Buffer} salt
|
|
* @returns {Buffer} HMAC
|
|
*/
|
|
|
|
crypto.hmac = function hmac(alg, data, salt) {
|
|
var hmac;
|
|
|
|
if (!nodeCrypto) {
|
|
hmac = hash.hmac(hash[alg], salt);
|
|
return new Buffer(hmac.update(data).digest());
|
|
}
|
|
|
|
hmac = nodeCrypto.createHmac(alg, salt);
|
|
return hmac.update(data).digest();
|
|
};
|
|
|
|
if (native)
|
|
crypto.hmac = native.hmac;
|
|
|
|
/**
|
|
* Perform key derivation using PBKDF2.
|
|
* @param {Buffer} key
|
|
* @param {Buffer} salt
|
|
* @param {Number} iter
|
|
* @param {Number} len
|
|
* @param {String} alg
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.pbkdf2 = function pbkdf2(key, salt, iter, len, alg) {
|
|
if (typeof key === 'string')
|
|
key = new Buffer(key, 'utf8');
|
|
|
|
if (typeof salt === 'string')
|
|
salt = new Buffer(salt, 'utf8');
|
|
|
|
if (nodeCrypto && nodeCrypto.pbkdf2Sync)
|
|
return nodeCrypto.pbkdf2Sync(key, salt, iter, len, alg);
|
|
|
|
return crypto._pbkdf2(key, salt, iter, len, alg);
|
|
};
|
|
|
|
/**
|
|
* Execute pbkdf2 asynchronously.
|
|
* @param {Buffer} key
|
|
* @param {Buffer} salt
|
|
* @param {Number} iter
|
|
* @param {Number} len
|
|
* @param {String} alg
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
crypto.pbkdf2Async = function pbkdf2Async(key, salt, iter, len, alg) {
|
|
var result;
|
|
|
|
if (typeof key === 'string')
|
|
key = new Buffer(key, 'utf8');
|
|
|
|
if (typeof salt === 'string')
|
|
salt = new Buffer(salt, 'utf8');
|
|
|
|
if (nodeCrypto && nodeCrypto.pbkdf2) {
|
|
return new Promise(function(resolve, reject) {
|
|
nodeCrypto.pbkdf2(key, salt, iter, len, alg, co.wrap(resolve, reject));
|
|
});
|
|
}
|
|
|
|
try {
|
|
result = crypto._pbkdf2(key, salt, iter, len, alg);
|
|
} catch (e) {
|
|
return Promise.reject(e);
|
|
}
|
|
|
|
return Promise.resolve(result);
|
|
};
|
|
|
|
/**
|
|
* Perform key derivation using scrypt.
|
|
* @param {Buffer} passwd
|
|
* @param {Buffer} salt
|
|
* @param {Number} N
|
|
* @param {Number} r
|
|
* @param {Number} p
|
|
* @param {Number} len
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.scrypt = function _scrypt(passwd, salt, N, r, p, len) {
|
|
if (typeof passwd === 'string')
|
|
passwd = new Buffer(passwd, 'utf8');
|
|
|
|
if (typeof salt === 'string')
|
|
salt = new Buffer(salt, 'utf8');
|
|
|
|
return scrypt(passwd, salt, N, r, p, len);
|
|
};
|
|
|
|
/**
|
|
* Execute scrypt asynchronously.
|
|
* @param {Buffer} passwd
|
|
* @param {Buffer} salt
|
|
* @param {Number} N
|
|
* @param {Number} r
|
|
* @param {Number} p
|
|
* @param {Number} len
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
crypto.scryptAsync = function _scrypt(passwd, salt, N, r, p, len) {
|
|
if (typeof passwd === 'string')
|
|
passwd = new Buffer(passwd, 'utf8');
|
|
|
|
if (typeof salt === 'string')
|
|
salt = new Buffer(salt, 'utf8');
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
scryptAsync(passwd, salt, N, r, p, len, co.wrap(resolve, reject));
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Derive a key using pbkdf2 with 50,000 iterations.
|
|
* @param {Buffer|String} passphrase
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
crypto.derive = function derive(passphrase) {
|
|
return crypto.pbkdf2Async(passphrase, 'bcoin', 50000, 32, 'sha256');
|
|
};
|
|
|
|
/**
|
|
* Encrypt with aes-256-cbc. Derives key with {@link crypto.derive}.
|
|
* @param {Buffer} data
|
|
* @param {Buffer|String} passphrase
|
|
* @param {Buffer} iv - 128 bit initialization vector.
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
crypto.encrypt = co(function* encrypt(data, passphrase, iv) {
|
|
var key;
|
|
|
|
assert(Buffer.isBuffer(data));
|
|
assert(passphrase, 'No passphrase.');
|
|
assert(Buffer.isBuffer(iv));
|
|
|
|
key = yield crypto.derive(passphrase);
|
|
|
|
try {
|
|
data = crypto.encipher(data, key, iv);
|
|
} catch (e) {
|
|
key.fill(0);
|
|
throw e;
|
|
}
|
|
|
|
key.fill(0);
|
|
|
|
return data;
|
|
});
|
|
|
|
/**
|
|
* Encrypt with aes-256-cbc.
|
|
* @param {Buffer} data
|
|
* @param {Buffer} key - 256 bit key.
|
|
* @param {Buffer} iv - 128 bit initialization vector.
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.encipher = function encipher(data, key, iv) {
|
|
var cipher;
|
|
|
|
assert(Buffer.isBuffer(data));
|
|
assert(Buffer.isBuffer(key));
|
|
assert(Buffer.isBuffer(iv));
|
|
assert(key.length === 32);
|
|
assert(iv.length === 16);
|
|
|
|
if (!nodeCrypto)
|
|
return aes.cbc.encrypt(data, key, iv);
|
|
|
|
cipher = nodeCrypto.createCipheriv('aes-256-cbc', key, iv);
|
|
|
|
return utils.concat(cipher.update(data), cipher.final());
|
|
};
|
|
|
|
/**
|
|
* Decrypt with aes-256-cbc. Derives key with {@link crypto.derive}.
|
|
* @param {Buffer} data
|
|
* @param {Buffer|String} passphrase
|
|
* @param {Buffer} iv - 128 bit initialization vector.
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
crypto.decrypt = co(function* decrypt(data, passphrase, iv) {
|
|
var key;
|
|
|
|
assert(Buffer.isBuffer(data));
|
|
assert(passphrase, 'No passphrase.');
|
|
assert(Buffer.isBuffer(iv));
|
|
|
|
key = yield crypto.derive(passphrase);
|
|
|
|
try {
|
|
data = crypto.decipher(data, key, iv);
|
|
} catch (e) {
|
|
key.fill(0);
|
|
throw e;
|
|
}
|
|
|
|
key.fill(0);
|
|
|
|
return data;
|
|
});
|
|
|
|
/**
|
|
* Decrypt with aes-256-cbc.
|
|
* @param {Buffer} data
|
|
* @param {Buffer} key - 256 bit key.
|
|
* @param {Buffer} iv - 128 bit initialization vector.
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.decipher = function decipher(data, key, iv) {
|
|
assert(Buffer.isBuffer(data));
|
|
assert(Buffer.isBuffer(key));
|
|
assert(Buffer.isBuffer(iv));
|
|
assert(key.length === 32);
|
|
assert(iv.length === 16);
|
|
try {
|
|
return crypto._decipher(data, key, iv);
|
|
} catch (e) {
|
|
throw new Error('Bad key for decryption.');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Decrypt with aes-256-cbc.
|
|
* @private
|
|
* @param {Buffer} data
|
|
* @param {Buffer} key
|
|
* @param {Buffer} iv
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto._decipher = function decipher(data, key, iv) {
|
|
var decipher;
|
|
|
|
if (!nodeCrypto)
|
|
return aes.cbc.decrypt(data, key, iv);
|
|
|
|
decipher = nodeCrypto.createDecipheriv('aes-256-cbc', key, iv);
|
|
|
|
return utils.concat(decipher.update(data), decipher.final());
|
|
};
|
|
|
|
/**
|
|
* Perform key derivation using PBKDF2.
|
|
* @private
|
|
* @param {Buffer} key
|
|
* @param {Buffer} salt
|
|
* @param {Number} iter
|
|
* @param {Number} len
|
|
* @param {String} alg
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto._pbkdf2 = function pbkdf2(key, salt, iter, len, alg) {
|
|
var size = crypto.hash(alg, new Buffer(0)).length;
|
|
var blocks = Math.ceil(len / size);
|
|
var out = new Buffer(len);
|
|
var buf = new Buffer(salt.length + 4);
|
|
var block = new Buffer(size);
|
|
var pos = 0;
|
|
var i, j, k, mac;
|
|
|
|
salt.copy(buf, 0);
|
|
|
|
for (i = 0; i < blocks; i++) {
|
|
buf.writeUInt32BE(i + 1, salt.length, true);
|
|
mac = crypto.hmac(alg, buf, key);
|
|
mac.copy(block, 0);
|
|
for (j = 1; j < iter; j++) {
|
|
mac = crypto.hmac(alg, mac, key);
|
|
for (k = 0; k < size; k++)
|
|
block[k] ^= mac[k];
|
|
}
|
|
block.copy(out, pos);
|
|
pos += size;
|
|
}
|
|
|
|
return out;
|
|
};
|
|
|
|
/**
|
|
* Perform hkdf extraction.
|
|
* @param {Buffer} ikm
|
|
* @param {Buffer} salt
|
|
* @param {String} alg
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.hkdfExtract = function hkdfExtract(ikm, salt, alg) {
|
|
return crypto.hmac(alg, ikm, salt);
|
|
};
|
|
|
|
/**
|
|
* 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;
|
|
};
|
|
|
|
/**
|
|
* 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;
|
|
};
|
|
|
|
/**
|
|
* Compare two bytes in constant time.
|
|
* @param {Number} a
|
|
* @param {Number} b
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
crypto.ceq = function ceq(a, b) {
|
|
var r = ~(a ^ b) & 0xff;
|
|
r &= r >>> 4;
|
|
r &= r >>> 2;
|
|
r &= r >>> 1;
|
|
return r === 1;
|
|
};
|
|
|
|
/**
|
|
* 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
|
|
&& utils.cmp(left, 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;
|
|
|
|
/**
|
|
* Generate some random bytes.
|
|
* @function
|
|
* @param {Number} size
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.randomBytes = random.randomBytes;
|
|
|
|
/**
|
|
* 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 = random.randomRange;
|
|
|
|
/**
|
|
* Generate a random uint32.
|
|
* Probably more cryptographically sound than
|
|
* `Math.random()`.
|
|
* @function
|
|
* @returns {Number}
|
|
*/
|
|
|
|
crypto.randomInt = random.randomInt;
|
|
|
|
/*
|
|
* Expose other objects.
|
|
*/
|
|
|
|
lazy('aes', './aes');
|
|
lazy('chachapoly', './chachapoly');
|
|
lazy('ec', './ec');
|
|
lazy('schnorr', './schnorr');
|
|
lazy('siphash', './siphash');
|