452 lines
8.4 KiB
JavaScript
452 lines
8.4 KiB
JavaScript
/*!
|
|
* crypto.js - crypto for bcoin
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* Copyright (c) 2014-2017, 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');
|
|
|
|
/**
|
|
* @exports crypto/crypto
|
|
* @ignore
|
|
*/
|
|
|
|
var crypto = exports;
|
|
|
|
/**
|
|
* Hash with chosen algorithm.
|
|
* @function
|
|
* @param {String} alg
|
|
* @param {Buffer} data
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
crypto.hash = backend.hash;
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* Create an HMAC.
|
|
* @function
|
|
* @param {String} alg
|
|
* @param {Buffer} data
|
|
* @param {Buffer} key
|
|
* @returns {Buffer} HMAC
|
|
*/
|
|
|
|
crypto.hmac = backend.hmac;
|
|
|
|
/**
|
|
* 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.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 = scrypt.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, Buffer.alloc(0)).length;
|
|
var blocks = Math.ceil(len / size);
|
|
var i, okm, buf, out;
|
|
|
|
if (blocks > 255)
|
|
throw new Error('Too many blocks.');
|
|
|
|
okm = Buffer.allocUnsafe(len);
|
|
|
|
if (blocks === 0)
|
|
return okm;
|
|
|
|
buf = Buffer.allocUnsafe(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.
|
|
* Note that this will mutate the `leaves` array!
|
|
* @param {Buffer[]} leaves
|
|
* @returns {MerkleTree}
|
|
*/
|
|
|
|
crypto.createMerkleTree = function createMerkleTree(leaves) {
|
|
var nodes = leaves;
|
|
var size = leaves.length;
|
|
var malleated = false;
|
|
var i, j, k, hash, left, right, lr;
|
|
|
|
if (size === 0) {
|
|
hash = Buffer.allocUnsafe(32);
|
|
hash.fill(0);
|
|
nodes.push(hash);
|
|
return new MerkleTree(nodes, malleated);
|
|
}
|
|
|
|
lr = Buffer.allocUnsafe(64);
|
|
|
|
for (j = 0; size > 1; size = ((size + 1) / 2) | 0) {
|
|
for (i = 0; i < size; i += 2) {
|
|
k = Math.min(i + 1, size - 1);
|
|
left = nodes[j + i];
|
|
right = nodes[j + k];
|
|
|
|
if (k === i + 1 && k + 1 === size
|
|
&& left.equals(right)) {
|
|
malleated = true;
|
|
}
|
|
|
|
left.copy(lr, 0);
|
|
right.copy(lr, 32);
|
|
|
|
hash = crypto.hash256(lr);
|
|
|
|
nodes.push(hash);
|
|
}
|
|
j += size;
|
|
}
|
|
|
|
return new MerkleTree(nodes, malleated);
|
|
};
|
|
|
|
if (native)
|
|
crypto.createMerkleTree = native.createMerkleTree;
|
|
|
|
/**
|
|
* Calculate merkle root from leaves.
|
|
* @param {Buffer[]} leaves
|
|
* @returns {MerkleRoot}
|
|
*/
|
|
|
|
crypto.createMerkleRoot = function createMerkleRoot(leaves) {
|
|
var tree = crypto.createMerkleTree(leaves);
|
|
var hash = tree.nodes[tree.nodes.length - 1];
|
|
var malleated = tree.malleated;
|
|
return new MerkleRoot(hash, malleated);
|
|
};
|
|
|
|
/**
|
|
* Collect a merkle branch at vector index.
|
|
* @param {Number} index
|
|
* @param {Buffer[]} leaves
|
|
* @returns {Buffer[]} branch
|
|
*/
|
|
|
|
crypto.createMerkleBranch = function createMerkleBranch(index, leaves) {
|
|
var size = leaves.length;
|
|
var tree = crypto.createMerkleTree(leaves);
|
|
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.nodes[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.verifyMerkleBranch = function verifyMerkleBranch(hash, branch, index) {
|
|
var i, otherside, lr;
|
|
|
|
if (branch.length === 0)
|
|
return hash;
|
|
|
|
lr = Buffer.allocUnsafe(64);
|
|
|
|
for (i = 0; i < branch.length; i++) {
|
|
otherside = branch[i];
|
|
|
|
if (index & 1) {
|
|
otherside.copy(lr, 0);
|
|
hash.copy(lr, 32);
|
|
} else {
|
|
hash.copy(lr, 0);
|
|
otherside.copy(lr, 32);
|
|
}
|
|
|
|
hash = crypto.hash256(lr);
|
|
index >>>= 1;
|
|
}
|
|
|
|
return hash;
|
|
};
|
|
|
|
if (native)
|
|
crypto.verifyMerkleBranch = native.verifyMerkleBranch;
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* 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);
|
|
};
|
|
|
|
/**
|
|
* Merkle Tree
|
|
* @constructor
|
|
* @ignore
|
|
* @param {Buffer[]} nodes
|
|
* @param {Boolean} malleated
|
|
*/
|
|
|
|
function MerkleTree(nodes, malleated) {
|
|
this.nodes = nodes;
|
|
this.malleated = malleated;
|
|
}
|
|
|
|
/**
|
|
* Merkle Root
|
|
* @constructor
|
|
* @ignore
|
|
* @param {Buffer} hash
|
|
* @param {Boolean} malleated
|
|
*/
|
|
|
|
function MerkleRoot(hash, malleated) {
|
|
this.hash = hash;
|
|
this.malleated = malleated;
|
|
}
|