diff --git a/lib/bip70/paymentrequest.js b/lib/bip70/paymentrequest.js index 1b72fa59..858d56b1 100644 --- a/lib/bip70/paymentrequest.js +++ b/lib/bip70/paymentrequest.js @@ -8,6 +8,7 @@ var assert = require('assert'); var util = require('../utils/util'); +var co = require('../utils/co'); var crypto = require('../crypto/crypto'); var x509 = require('./x509'); var PEM = require('../utils/pem'); @@ -226,6 +227,34 @@ PaymentRequest.prototype.verifyChain = function verifyChain() { return x509.verifyChain(this.getChain()); }; +PaymentRequest.prototype.verifyAsync = co(function* verifyAsync() { + var alg, msg, sig, chain; + + if (!this.pkiType || this.pkiType === 'none') + return true; + + if (!this.signature) + return false; + + alg = this.getAlgorithm(); + + if (!alg) + return false; + + msg = this.signatureData(); + sig = this.signature; + chain = this.getChain(); + + return yield x509.verifySubjectAsync(alg.hash, msg, sig, chain); +}); + +PaymentRequest.prototype.verifyChainAsync = co(function* verifyChain() { + if (!this.pkiType || this.pkiType === 'none') + return true; + + return yield x509.verifyChainAsync(this.getChain()); +}); + PaymentRequest.prototype.getCA = function getCA() { var chain, root; diff --git a/lib/bip70/pk.js b/lib/bip70/pk.js index 681b05b2..26a27d04 100644 --- a/lib/bip70/pk.js +++ b/lib/bip70/pk.js @@ -7,6 +7,7 @@ 'use strict'; var pk = require('../crypto/pk'); +var co = require('../utils/co'); exports._verify = function verify(hash, msg, sig, key) { switch (key.alg) { @@ -41,3 +42,37 @@ exports.sign = function sign(hash, msg, key) { throw new Error('Unsupported algorithm.'); } }; + +exports._verifyAsync = co(function* verifyAsync(hash, msg, sig, key) { + switch (key.alg) { + case 'dsa': + return yield pk.dsa.verifyAsync(hash, msg, sig, key.data, key.params); + case 'rsa': + return yield pk.rsa.verifyAsync(hash, msg, sig, key.data); + case 'ecdsa': + return yield pk.ecdsa.verifyAsync(key.curve, hash, msg, sig, key.data); + default: + throw new Error('Unsupported algorithm.'); + } +}); + +exports.verifyAsync = co(function* verifyAsync(hash, msg, sig, key) { + try { + return yield exports._verifyAsync(hash, msg, sig, key); + } catch (e) { + return false; + } +}); + +exports.signAsync = co(function* signAsync(hash, msg, key) { + switch (key.alg) { + case 'dsa': + return yield pk.dsa.signAsync(hash, msg, key.data, key.params); + case 'rsa': + return yield pk.rsa.signAsync(hash, msg, key.data); + case 'ecdsa': + return yield pk.ecdsa.signAsync(key.curve, hash, msg, key.data); + default: + throw new Error('Unsupported algorithm.'); + } +}); diff --git a/lib/bip70/x509.js b/lib/bip70/x509.js index 180b15a4..61e02064 100644 --- a/lib/bip70/x509.js +++ b/lib/bip70/x509.js @@ -12,6 +12,7 @@ var PEM = require('../utils/pem'); var util = require('../utils/util'); var crypto = require('../crypto/crypto'); var pk = require('./pk'); +var co = require('../utils/co'); var x509 = exports; x509.getSubjectOID = function getSubjectOID(cert, oid) { @@ -177,11 +178,11 @@ x509.getPublicKey = function getPublicKey(cert) { x509.verifyTime = function verifyTime(cert) { var time = cert.tbs.validity; - var now = Math.floor(Date.now() / 1000); + var now = util.now(); return now > time.notBefore && now < time.notAfter; }; -x509.signSubject = function signSubject(hash, msg, key, chain) { +x509.getSigningKey = function getSigningKey(key, chain) { var cert, pub, curve; assert(chain.length !== 0, 'No chain available.'); @@ -210,10 +211,20 @@ x509.signSubject = function signSubject(hash, msg, key, chain) { }; } - return pk.sign(hash, msg, key); + return key; }; -x509.verifySubject = function verifySubject(hash, msg, sig, chain) { +x509.signSubject = function signSubject(hash, msg, key, chain) { + var priv = x509.getSigningKey(key, chain); + return pk.sign(hash, msg, priv); +}; + +x509.signSubjectAsync = co(function* signSubjectAsync(hash, msg, key, chain) { + var priv = x509.getSigningKey(key, chain); + return yield pk.signAsync(hash, msg, priv); +}); + +x509.getVerifyKey = function getVerifyKey(chain) { var cert, key; if (chain.length === 0) @@ -229,27 +240,82 @@ x509.verifySubject = function verifySubject(hash, msg, sig, chain) { if (!key) return false; + return key; +}; + +x509.verifySubject = function verifySubject(hash, msg, sig, chain) { + var key = x509.getVerifyKey(chain); return pk.verify(hash, msg, sig, key); }; -x509.verifyChain = function verifyChain(chain) { +x509.verifySubjectAsync = co(function* verifySubjectAsync(hash, msg, sig, chain) { + var key = x509.getVerifyKey(chain); + return yield pk.verifyAsync(hash, msg, sig, key); +}); + +x509.parseChain = function parseChain(chain) { + var certs = []; + var i, cert; + + for (i = 0; i < chain.length; i++) { + cert = x509.parse(chain[i]); + + if (!cert) + return; + + certs.push(cert); + } + + return certs; +}; + +x509.verifyTimes = function verifyTimes(chain) { + var i, cert; + + for (i = 0; i < chain.length; i++) { + cert = chain[i]; + if (!x509.verifyTime(cert)) + return false; + } + + return true; +}; + +x509.verifyTrust = function verifyTrust(chain) { + var i, cert; + + // If trust hasn't been + // setup, just return. + if (x509.allowUntrusted) + return true; + + // Make sure we trust one + // of the certs in the chain. + for (i = 0; i < chain.length; i++) { + cert = chain[i]; + + // If any certificate in the chain + // is trusted, assume we also trust + // the parent. + if (x509.isTrusted(cert)) + return true; + } + + // No trusted certs present. + return false; +}; + +x509.verifyChain = function verifyChain(certs) { + var chain = x509.parseChain(certs); var i, child, parent, alg, key, sig, msg; - chain = chain.slice(); + if (!chain) + return false; // Parse certificates and // check validity time. - for (i = 0; i < chain.length; i++) { - child = x509.parse(chain[i]); - - if (!child) - return false; - - chain[i] = child; - - if (!x509.verifyTime(child)) - return false; - } + if (!x509.verifyTimes(chain)) + return false; // Verify signatures. for (i = 1; i < chain.length; i++) { @@ -271,26 +337,47 @@ x509.verifyChain = function verifyChain(chain) { return false; } - // If trust hasn't been - // setup, just return. - if (x509.allowUntrusted) - return true; + // Make sure we trust one + // of the certs in the chain. + return x509.verifyTrust(chain); +}; + +x509.verifyChainAsync = co(function* verifyChainAsync(certs) { + var chain = x509.parseChain(certs); + var i, child, parent, alg, key, sig, msg; + + if (!chain) + return false; + + // Parse certificates and + // check validity time. + if (!x509.verifyTimes(chain)) + return false; + + // Verify signatures. + for (i = 1; i < chain.length; i++) { + child = chain[i - 1]; + parent = chain[i]; + + alg = x509.getSigAlgorithm(child); + msg = child.tbs.raw; + sig = child.sig; + key = x509.getPublicKey(parent); + + if (!alg || !alg.hash) + return false; + + if (!key) + return false; + + if (!(yield pk.verifyAsync(alg.hash, msg, sig, key))) + return false; + } // Make sure we trust one // of the certs in the chain. - for (i = 0; i < chain.length; i++) { - child = chain[i]; - - // If any certificate in the chain - // is trusted, assume we also trust - // the parent. - if (x509.isTrusted(child)) - return true; - } - - // No trusted certs present. - return false; -}; + return x509.verifyTrust(chain); +}); function isHash(data) { if (typeof data === 'string') diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 7dc13e1f..8fe4d1d0 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -1318,6 +1318,8 @@ Chain.prototype._add = co(function* add(block) { 'error parsing message', 100); } + if (util.isBrowser) + yield block.cacheHashes(); } // Update the block height early diff --git a/lib/crypto/aes.js b/lib/crypto/aes.js index 7243efbb..2e61e881 100644 --- a/lib/crypto/aes.js +++ b/lib/crypto/aes.js @@ -33,14 +33,22 @@ function AESKey(key, bits) { this.userKey = key; this.bits = bits; - if (this.bits === 128) - this.rounds = 10; - else if (this.bits === 192) - this.rounds = 12; - else if (this.bits === 256) - this.rounds = 14; - else - throw new Error('Bad key size.'); + switch (this.bits) { + case 128: + this.rounds = 10; + break; + case 192: + this.rounds = 12; + break; + case 256: + this.rounds = 14; + break; + default: + throw new Error('Bad key size.'); + } + + assert(Buffer.isBuffer(key)); + assert(key.length === this.bits / 8); this.decryptKey = null; this.encryptKey = null; @@ -688,6 +696,8 @@ AES.ecb = {}; */ AES.ecb.encrypt = function encrypt(data, key) { + assert(Buffer.isBuffer(data)); + assert(key.length === 32); return AES.encrypt(data, key, null, 256, 'ecb'); }; @@ -699,6 +709,8 @@ AES.ecb.encrypt = function encrypt(data, key) { */ AES.ecb.decrypt = function decrypt(data, key) { + assert(Buffer.isBuffer(data)); + assert(key.length === 32); return AES.decrypt(data, key, null, 256, 'ecb'); }; @@ -717,6 +729,9 @@ AES.cbc = {}; */ AES.cbc.encrypt = function encrypt(data, key, iv) { + assert(Buffer.isBuffer(data)); + assert(key.length === 32); + assert(iv.length === 16); return AES.encrypt(data, key, iv, 256, 'cbc'); }; @@ -729,6 +744,9 @@ AES.cbc.encrypt = function encrypt(data, key, iv) { */ AES.cbc.decrypt = function decrypt(data, key, iv) { + assert(Buffer.isBuffer(data)); + assert(key.length === 32); + assert(iv.length === 16); return AES.decrypt(data, key, iv, 256, 'cbc'); }; diff --git a/lib/crypto/backend-browser.js b/lib/crypto/backend-browser.js index a41b6ee9..fa4bc175 100644 --- a/lib/crypto/backend-browser.js +++ b/lib/crypto/backend-browser.js @@ -4,57 +4,168 @@ * https://github.com/bcoin-org/bcoin */ -/* jshint worker: true */ - 'use strict'; var assert = require('assert'); var hashjs = require('hash.js'); +var util = require('../utils/util'); var aes = require('./aes'); +var global = util.global; +var crypto = global.crypto || global.msCrypto || {}; +var subtle = crypto.subtle && crypto.subtle.importKey ? crypto.subtle : {}; var backend = exports; -var global, crypto, subtle; -global = (function() { - if (typeof window !== 'undefined') - return window; +/* + * Hashing + */ - if (typeof self !== 'undefined') - return self; - - throw new Error('No global found.'); -})(); - -crypto = global.crypto || global.msCrypto; -subtle = crypto ? crypto.subtle : null; - -backend.hash = function hash(alg, data) { - return new Buffer(hashjs[alg]().update(data).digest()); +backend.hash = function _hash(alg, data) { + var hash = hashjs[alg]; + assert(hash != null, 'Unknown algorithm.'); + return new Buffer(hash().update(data).digest()); }; -backend.hmac = function hmac(alg, data, salt) { +backend.ripemd160 = function ripemd160(data) { + return backend.hash('ripemd160', data); +}; + +backend.sha1 = function sha1(data) { + return backend.hash('sha1', data); +}; + +backend.sha256 = function sha256(data) { + return backend.hash('sha256', data); +}; + +backend.hash160 = function hash160(data) { + return backend.ripemd160(backend.sha256(data)); +}; + +backend.hash256 = function hash256(data) { + return backend.sha256(backend.sha256(data)); +}; + +backend.hmac = function _hmac(alg, data, key) { var hash = hashjs[alg]; var hmac; assert(hash != null, 'Unknown algorithm.'); - hmac = hashjs.hmac(hash, salt); + hmac = hashjs.hmac(hash, key); return new Buffer(hmac.update(data).digest()); }; -backend.pbkdf2 = null; +backend.hashAsync = function hashAsync(alg, data) { + var name = backend.getHash(alg); + var result; + + if (!name) { + try { + result = backend.hash(alg, data); + } catch (e) { + return Promise.reject(e); + } + return Promise.resolve(result); + } + + return subtle.digest(name, data).then(function(hash) { + return new Buffer(hash); + }); +}; + +if (!subtle.digest) + backend.hashAsync = util.promisify(backend.hash); + +backend.hash256Async = function hash256Async(data) { + return backend.hashAsync('sha256', data).then(function(hash) { + return backend.hashAsync('sha256', hash); + }); +}; + +backend.hmacAsync = function _hmacAsync(alg, data, key) { + var name = backend.getHash(alg); + var use = ['sign']; + var algo, promise, result; + + if (!name) { + try { + result = backend.hmac(alg, data, key); + } catch (e) { + return Promise.reject(e); + } + return Promise.resolve(result); + } + + algo = { + name: 'HMAC', + hash: name + }; + + promise = subtle.importKey('raw', key, algo, true, use); + + return promise.then(function(key) { + return subtle.sign('HMAC', key, data); + }).then(function(data) { + return new Buffer(data); + }); +}; + +if (!subtle.sign) + backend.hmacAsync = util.promisify(backend.hmac); + +/* + * Key Derivation + */ + +backend.pbkdf2 = function pbkdf2(key, salt, iter, len, alg) { + var size = backend.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 = backend.hmac(alg, buf, key); + mac.copy(block, 0); + for (j = 1; j < iter; j++) { + mac = backend.hmac(alg, mac, key); + for (k = 0; k < size; k++) + block[k] ^= mac[k]; + } + block.copy(out, pos); + pos += size; + } + + return out; +}; backend.pbkdf2Async = function pbkdf2Async(key, salt, iter, len, alg) { var algo = { name: 'PBKDF2' }; var use = ['deriveBits']; + var name = backend.getHash(alg); var length = len * 8; - var options, promise; + var options, promise, result; + + if (!name) { + try { + result = backend.pbkdf2(key, salt, iter, len, alg); + } catch (e) { + return Promise.reject(e); + } + return Promise.resolve(result); + } options = { name: 'PBKDF2', salt: salt, iterations: iter, - hash: getHash(alg) + hash: name }; promise = subtle.importKey('raw', key, algo, false, use); @@ -66,8 +177,12 @@ backend.pbkdf2Async = function pbkdf2Async(key, salt, iter, len, alg) { }); }; -if (!subtle || !subtle.importKey || !subtle.deriveBits) - backend.pbkdf2Async = null; +if (!subtle.deriveBits) + backend.pbkdf2Async = util.promisify(backend.pbkdf2); + +/* + * Ciphers + */ backend.encipher = function encipher(data, key, iv) { return aes.cbc.encrypt(data, key, iv); @@ -81,13 +196,53 @@ backend.decipher = function decipher(data, key, iv) { } }; +backend.encipherAsync = function encipherAsync(data, key, iv) { + var algo = { name: 'AES-CBC' }; + var use = ['encrypt']; + var options = { name: 'AES-CBC', iv: iv }; + var promise; + + promise = subtle.importKey('raw', key, algo, false, use); + + return promise.then(function(key) { + return subtle.encrypt(options, key, data); + }).then(function(result) { + return new Buffer(result); + }); +}; + +if (!subtle.encrypt) + backend.encipherAsync = util.promisify(backend.encipher); + +backend.decipherAsync = function decipherAsync(data, key, iv) { + var algo = { name: 'AES-CBC' }; + var use = ['decrypt']; + var options = { name: 'AES-CBC', iv: iv }; + var promise; + + promise = subtle.importKey('raw', key, algo, false, use); + + return promise.then(function(key) { + return subtle.decrypt(options, key, data); + }).then(function(result) { + return new Buffer(result); + }); +}; + +if (!subtle.decrypt) + backend.decipherAsync = util.promisify(backend.decipher); + +/* + * Misc + */ + backend.randomBytes = function randomBytes(n) { var data = new Uint8Array(n); crypto.getRandomValues(data); return new Buffer(data.buffer); }; -if (!crypto || !crypto.getRandomValues) { +if (!crypto.getRandomValues) { // Out of luck here. Use bad randomness for now. backend.randomBytes = function randomBytes(n) { var data = new Buffer(n); @@ -100,7 +255,7 @@ if (!crypto || !crypto.getRandomValues) { }; } -function getHash(name) { +backend.getHash = function getHash(name) { switch (name) { case 'sha1': return 'SHA-1'; @@ -111,6 +266,9 @@ function getHash(name) { case 'sha512': return 'SHA-512'; default: - throw new Error('Unknown hash.'); + return null; } -} +}; + +backend.crypto = crypto; +backend.subtle = subtle; diff --git a/lib/crypto/backend.js b/lib/crypto/backend.js index ed85d19e..f60b53e8 100644 --- a/lib/crypto/backend.js +++ b/lib/crypto/backend.js @@ -7,31 +7,78 @@ 'use strict'; var util = require('../utils/util'); +var co = require('../utils/co'); var crypto = require('crypto'); +var native = require('../utils/native').binding; var backend = exports; +if (!crypto.pbkdf2Sync) + throw new Error('This modules requires node.js v0.11.0 or above.'); + +/* + * Hashing + */ + backend.hash = function hash(alg, data) { return crypto.createHash(alg).update(data).digest(); }; -backend.hmac = function hmac(alg, data, salt) { - var hmac = crypto.createHmac(alg, salt); +backend.ripemd160 = function ripemd160(data) { + return backend.hash('ripemd160', data); +}; + +backend.sha1 = function sha1(data) { + return backend.hash('sha1', data); +}; + +backend.sha256 = function sha256(data) { + return backend.hash('sha256', data); +}; + +backend.hash160 = function hash160(data) { + return backend.ripemd160(backend.sha256(data)); +}; + +backend.hash256 = function hash256(data) { + return backend.sha256(backend.sha256(data)); +}; + +backend.hmac = function hmac(alg, data, key) { + var hmac = crypto.createHmac(alg, key); return hmac.update(data).digest(); }; +if (native) { + backend.hash = native.hash; + backend.hmac = native.hmac; + backend.ripemd160 = native.ripemd160; + backend.sha1 = native.sha1; + backend.sha256 = native.sha256; + backend.hash160 = native.hash160; + backend.hash256 = native.hash256; +} + +backend.hashAsync = util.promisify(backend.hash); +backend.hash256Async = util.promisify(backend.hash256); +backend.hmacAsync = util.promisify(backend.hmac); + +/* + * Key Derivation + */ + backend.pbkdf2 = function pbkdf2(key, salt, iter, len, alg) { return crypto.pbkdf2Sync(key, salt, iter, len, alg); }; -if (!crypto.pbkdf2Sync) - backend.pbkdf2 = null; - -backend.pbkdf2Async = function pbkdf2Async(key, salt, iter, len, alg, callback) { - return crypto.pbkdf2(key, salt, iter, len, alg, callback); +backend.pbkdf2Async = function pbkdf2Async(key, salt, iter, len, alg) { + return new Promise(function(resolve, reject) { + crypto.pbkdf2(key, salt, iter, len, alg, co.wrap(resolve, reject)); + }); }; -if (!crypto.pbkdf2) - backend.pbkdf2Async = null; +/* + * Ciphers + */ backend.encipher = function encipher(data, key, iv) { var cipher = crypto.createCipheriv('aes-256-cbc', key, iv); @@ -47,4 +94,16 @@ backend.decipher = function decipher(data, key, iv) { } }; +if (native) { + backend.encipher = native.encipher; + backend.decipher = native.decipher; +} + +backend.encipherAsync = util.promisify(backend.encipher); +backend.decipherAsync = util.promisify(backend.decipher); + +/* + * Misc + */ + backend.randomBytes = crypto.randomBytes; diff --git a/lib/crypto/crypto.js b/lib/crypto/crypto.js index 820eb95a..6d7a2917 100644 --- a/lib/crypto/crypto.js +++ b/lib/crypto/crypto.js @@ -7,114 +7,111 @@ 'use strict'; -var assert = require('assert'); +var backend = require('./backend'); +var native = require('../utils/native').binding; var scrypt = require('./scrypt'); var scryptAsync = require('./scrypt-async'); -var co = require('../utils/co'); -var native = require('../utils/native').binding; -var backend = require('./backend'); var crypto = exports; /** * Hash with chosen algorithm. + * @function * @param {String} alg * @param {Buffer} data * @returns {Buffer} */ -crypto.hash = function _hash(alg, data) { - return backend.hash(alg, data); -}; +crypto.hash = backend.hash; -if (native) - crypto.hash = native.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 = function ripemd160(data) { - return crypto.hash('ripemd160', data); -}; +crypto.ripemd160 = backend.ripemd160; /** * Hash with sha1. + * @function * @param {Buffer} data * @returns {Buffer} */ -crypto.sha1 = function sha1(data) { - return crypto.hash('sha1', data); -}; +crypto.sha1 = backend.sha1; /** * Hash with sha256. + * @function * @param {Buffer} data * @returns {Buffer} */ -crypto.sha256 = function sha256(data) { - return crypto.hash('sha256', data); -}; - -if (native) - crypto.sha256 = native.sha256; +crypto.sha256 = backend.sha256; /** * Hash with sha256 and ripemd160 (OP_HASH160). + * @function * @param {Buffer} data * @returns {Buffer} */ -crypto.hash160 = function hash160(data) { - return crypto.ripemd160(crypto.sha256(data)); -}; - -if (native) - crypto.hash160 = native.hash160; +crypto.hash160 = backend.hash160; /** * Hash with sha256 twice (OP_HASH256). + * @function * @param {Buffer} data * @returns {Buffer} */ -crypto.hash256 = function hash256(data) { - return crypto.sha256(crypto.sha256(data)); -}; - -if (native) - crypto.hash256 = native.hash256; +crypto.hash256 = backend.hash256; /** - * Create a sha256 checksum (common in bitcoin). + * Hash with sha256 twice (async). + * @function * @param {Buffer} data * @returns {Buffer} */ -crypto.checksum = function checksum(data) { - return crypto.hash256(data).slice(0, 4); -}; +crypto.hash256Async = backend.hash256Async; /** * Create an HMAC. + * @function * @param {String} alg * @param {Buffer} data - * @param {Buffer} salt + * @param {Buffer} key * @returns {Buffer} HMAC */ -crypto.hmac = function hmac(alg, data, salt) { - return backend.hmac(alg, data, salt); -}; +crypto.hmac = backend.hmac; -if (native) - crypto.hmac = native.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 @@ -123,21 +120,11 @@ if (native) * @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 (backend.pbkdf2) - return backend.pbkdf2(key, salt, iter, len, alg); - - return crypto._pbkdf2(key, salt, iter, len, alg); -}; +crypto.pbkdf2 = backend.pbkdf2; /** * Execute pbkdf2 asynchronously. + * @function * @param {Buffer} key * @param {Buffer} salt * @param {Number} iter @@ -146,32 +133,11 @@ crypto.pbkdf2 = function pbkdf2(key, salt, iter, len, 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 (backend.pbkdf2Async) { - return new Promise(function(resolve, reject) { - backend.pbkdf2Async(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); -}; +crypto.pbkdf2Async = backend.pbkdf2Async; /** * Perform key derivation using scrypt. + * @function * @param {Buffer} passwd * @param {Buffer} salt * @param {Number} N @@ -181,18 +147,11 @@ crypto.pbkdf2Async = function pbkdf2Async(key, salt, iter, len, alg) { * @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); -}; +crypto.scrypt = scrypt; /** * Execute scrypt asynchronously. + * @function * @param {Buffer} passwd * @param {Buffer} salt * @param {Number} N @@ -202,66 +161,18 @@ crypto.scrypt = function _scrypt(passwd, salt, N, r, p, 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)); - }); -}; - -/** - * 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; -}; +crypto.scryptAsync = scryptAsync; /** * Perform hkdf extraction. * @param {Buffer} ikm - * @param {Buffer} salt + * @param {Buffer} key * @param {String} alg * @returns {Buffer} */ -crypto.hkdfExtract = function hkdfExtract(ikm, salt, alg) { - return crypto.hmac(alg, ikm, salt); +crypto.hkdfExtract = function hkdfExtract(ikm, key, alg) { + return crypto.hmac(alg, ikm, key); }; /** @@ -424,38 +335,47 @@ if (native) /** * 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 = function encipher(data, key, iv) { - assert(Buffer.isBuffer(data)); - assert(Buffer.isBuffer(key)); - assert(Buffer.isBuffer(iv)); - assert(key.length === 32); - assert(iv.length === 16); +crypto.encipher = backend.encipher; - return backend.encipher(data, key, iv); -}; +/** + * 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 = 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); - return backend.decipher(data, key, iv); -}; +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). @@ -488,21 +408,6 @@ crypto.ccmp = function ccmp(a, b) { 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; -}; - /** * A maybe-secure memzero. * @param {Buffer} data diff --git a/lib/crypto/ec-secp256k1.js b/lib/crypto/ec-secp256k1.js index dbbd9ba3..afa68913 100644 --- a/lib/crypto/ec-secp256k1.js +++ b/lib/crypto/ec-secp256k1.js @@ -9,7 +9,7 @@ var assert = require('assert'); var util = require('../utils/util'); -var crypto = require('./crypto'); +var backend = require('./backend'); var secp256k1 = require('secp256k1'); /* @@ -40,7 +40,7 @@ ec.generatePrivateKey = function generatePrivateKey() { var priv; do { - priv = crypto.randomBytes(32); + priv = backend.randomBytes(32); } while (!secp256k1.privateKeyVerify(priv)); return priv; diff --git a/lib/crypto/index.js b/lib/crypto/index.js index 64b30b43..952f4dae 100644 --- a/lib/crypto/index.js +++ b/lib/crypto/index.js @@ -4,13 +4,15 @@ var crypto = require('./crypto'); exports.crypto = crypto; exports.hash = crypto.hash; +exports.hashAsync = crypto.hashAsync; exports.ripemd160 = crypto.ripemd160; exports.sha1 = crypto.sha1; exports.sha256 = crypto.sha256; exports.hash160 = crypto.hash160; exports.hash256 = crypto.hash256; -exports.checksum = crypto.checksum; +exports.hash256Async = crypto.hash256Async; exports.hmac = crypto.hmac; +exports.hmacAsync = crypto.hmacAsync; exports.pbkdf2 = crypto.pbkdf2; exports.pbkdf2Async = crypto.pbkdf2Async; exports.scrypt = crypto.scrypt; @@ -24,7 +26,6 @@ exports.checkMerkleBranch = crypto.checkMerkleBranch; exports.encipher = crypto.encipher; exports.decipher = crypto.decipher; exports.ccmp = crypto.ccmp; -exports.ceq = crypto.ceq; exports.cleanse = crypto.cleanse; exports.randomBytes = crypto.randomBytes; exports.randomInt = crypto.randomInt; diff --git a/lib/crypto/pk-browser.js b/lib/crypto/pk-browser.js index 02f7b080..b42a1308 100644 --- a/lib/crypto/pk-browser.js +++ b/lib/crypto/pk-browser.js @@ -9,8 +9,11 @@ var assert = require('assert'); var BN = require('bn.js'); var ASN1 = require('../utils/asn1'); +var util = require('../utils/util'); +var co = require('../utils/co'); var elliptic = require('elliptic'); -var crypto = require('./crypto'); +var backend = require('./backend'); +var subtle = backend.subtle; var dsa, rsa, ecdsa; /* @@ -23,10 +26,14 @@ dsa.verify = function verify(alg, msg, sig, key, params) { throw new Error('DSA not implemented.'); }; +dsa.verifyAsync = util.promisify(dsa.verify); + dsa.sign = function sign(alg, msg, key, params) { throw new Error('DSA not implemented.'); }; +dsa.signAsync = util.promisify(dsa.sign); + /* * RSA */ @@ -52,7 +59,7 @@ rsa.verify = function verify(alg, msg, sig, key) { if (!prefix) throw new Error('Unknown PKCS prefix.'); - hash = crypto.hash(alg, msg); + hash = backend.hash(alg, msg); len = prefix.length + hash.length; pub = ASN1.parseRSAPublic(key); @@ -66,14 +73,14 @@ rsa.verify = function verify(alg, msg, sig, key) { m = rsa.encrypt(N, e, sig); em = leftpad(m, k); - ok = crypto.ceq(em[0], 0x00); - ok &= crypto.ceq(em[1], 0x01); - ok &= crypto.ccmp(em.slice(k - hash.length, k), hash); - ok &= crypto.ccmp(em.slice(k - len, k - hash.length), prefix); - ok &= crypto.ceq(em[k - len - 1], 0x00); + ok = ceq(em[0], 0x00); + ok &= ceq(em[1], 0x01); + ok &= backend.ccmp(em.slice(k - hash.length, k), hash); + ok &= backend.ccmp(em.slice(k - len, k - hash.length), prefix); + ok &= ceq(em[k - len - 1], 0x00); for (i = 2; i < k - len - 1; i++) - ok &= crypto.ceq(em[i], 0xff); + ok &= ceq(em[i], 0xff); return ok === 1; }; @@ -86,7 +93,7 @@ rsa.sign = function sign(alg, msg, key) { if (!prefix) throw new Error('Unknown PKCS prefix.'); - hash = crypto.hash(alg, msg); + hash = backend.hash(alg, msg); len = prefix.length + hash.length; priv = ASN1.parseRSAPrivate(key); @@ -110,6 +117,82 @@ rsa.sign = function sign(alg, msg, key) { return rsa.decrypt(N, D, em); }; +rsa.verifyAsync = co(function* verifyAsync(alg, msg, sig, key) { + var use = ['verify']; + var name = backend.getHash(alg); + var pub, data, algo, ckey; + + if (!name) + return rsa.verify(alg, msg, sig, key); + + pub = ASN1.parseRSAPublic(key); + + data = { + kty: 'RSA', + n: toBase64(pub.modulus), + e: toBase64(pub.publicExponent), + alg: 'RS256', + ext: true + }; + + algo = { + name: 'RSASSA-PKCS1-v1_5', + hash: { name: name } + }; + + ckey = yield subtle.importKey('jwk', data, algo, false, use); + + algo = { + name: 'RSASSA-PKCS1-v1_5', + }; + + return yield subtle.verify(algo, ckey, sig, msg); +}); + +if (!subtle.verify) + rsa.verifyAsync = util.promisify(rsa.verify); + +rsa.signAsync = co(function* signAsync(alg, msg, key) { + var use = ['sign']; + var name = backend.getHash(alg); + var pub, data, algo, ckey; + + if (!name) + return rsa.sign(alg, msg, key); + + pub = ASN1.parseRSAPrivate(key); + + data = { + kty: 'RSA', + n: toBase64(pub.modulus), + e: toBase64(pub.publicExponent), + d: toBase64(pub.privateExponent), + p: toBase64(pub.prime1), + q: toBase64(pub.prime2), + dp: toBase64(pub.exponent1), + dq: toBase64(pub.exponent2), + qi: toBase64(pub.coefficient), + alg: 'RS256', + ext: true + }; + + algo = { + name: 'RSASSA-PKCS1-v1_5', + hash: { name: name } + }; + + ckey = yield subtle.importKey('jwk', data, algo, false, use); + + algo = { + name: 'RSASSA-PKCS1-v1_5', + }; + + return yield subtle.sign(algo, ckey, msg); +}); + +if (!subtle.sign) + rsa.signAsync = util.promisify(rsa.sign); + rsa.decrypt = function decrypt(N, D, m) { var c = new BN(m); @@ -137,28 +220,91 @@ rsa.encrypt = function encrypt(N, e, m) { ecdsa = {}; -ecdsa.verify = function verify(curve, msg, alg, key, sig) { +ecdsa.verify = function verify(curve, alg, msg, key, sig) { var ec, hash; assert(curve, 'No curve selected.'); ec = elliptic.ec(curve); - hash = crypto.hash(alg, msg); + hash = backend.hash(alg, msg); return ec.verify(hash, sig, key); }; -ecdsa.sign = function sign(curve, msg, alg, key) { +ecdsa.sign = function sign(curve, alg, msg, key) { var ec, hash; assert(curve, 'No curve selected.'); ec = elliptic.ec(curve); - hash = crypto.hash(alg, msg); + hash = backend.hash(alg, msg); return new Buffer(ec.sign(hash, key)); }; +ecdsa.verifyAsync = co(function* verifyAsync(curve, alg, msg, sig, key) { + var use = ['verify']; + var name = backend.getHash(alg); + var curveName = getCurve(curve); + var pub, data, algo, ckey; + + if (!name || !curveName) + return ecdsa.verify(curve, alg, msg, sig, key); + + pub = parseECPublic(key, curve); + + data = { + kty: 'EC', + x: toBase64(pub.x), + y: toBase64(pub.y), + ext: true + }; + + algo = { + name: 'ECDSA', + namedCurve: curveName + }; + + ckey = yield subtle.importKey('jwk', data, algo, false, use); + + algo = { + name: 'ECDSA', + hash: name + }; + + return yield subtle.verify(algo, ckey, sig, msg); +}); + +if (!subtle.verify) + ecdsa.verifyAsync = util.promisify(ecdsa.verify); + +ecdsa.signAsync = co(function* signAsync(curve, alg, msg, key) { + var use = ['sign']; + var name = backend.getHash(alg); + var curveName = getCurve(curve); + var algo, ckey; + + if (!name || !curveName) + return ecdsa.sign(curve, alg, msg, key); + + algo = { + name: 'ECDSA', + namedCurve: curveName + }; + + ckey = yield subtle.importKey('raw', key, algo, false, use); + + algo = { + name: 'ECDSA', + hash: name + }; + + return yield subtle.sign(algo, ckey, msg); +}); + +if (!subtle.sign) + ecdsa.signAsync = util.promisify(ecdsa.sign); + /* * Helpers */ @@ -178,6 +324,44 @@ function leftpad(input, size) { return out; } +function toBase64(data) { + var str = data.toString('base64'); + str = str.replace(/\+/g, '-'); + str = str.replace(/\//g, '_'); + str = str.replace(/=+$/, ''); + return str; +} + +function getCurve(name) { + switch (name) { + case 'p256': + return 'P-256'; + case 'p384': + return 'P-384'; + case 'p521': + return 'P-521'; + default: + return null; + } +} + +function parseECPublic(data, curve) { + var ec = elliptic.ec(curve).curve; + var point = ec.decodePoint(data); + return { + x: point.toArrayLike(Buffer, 'be', 32), + y: point.toArrayLike(Buffer, 'be', 32) + }; +} + +function ceq(a, b) { + var r = ~(a ^ b) & 0xff; + r &= r >>> 4; + r &= r >>> 2; + r &= r >>> 1; + return r === 1; +} + /* * Expose */ diff --git a/lib/crypto/pk.js b/lib/crypto/pk.js index 4decbd7b..08171e42 100644 --- a/lib/crypto/pk.js +++ b/lib/crypto/pk.js @@ -9,7 +9,8 @@ var assert = require('assert'); var PEM = require('../utils/pem'); var elliptic = require('elliptic'); -var crypto = require('./crypto'); +var util = require('../utils/util'); +var backend = require('./backend'); var nodeCrypto = require('crypto'); var dsa, rsa, ecdsa; @@ -29,6 +30,9 @@ dsa.sign = function _sign(alg, msg, key, params) { return sign('dsa', alg, msg, pem); }; +dsa.verifyAsync = util.promisify(dsa.verify); +dsa.signAsync = util.promisify(dsa.sign); + /* * RSA */ @@ -45,6 +49,9 @@ rsa.sign = function _sign(alg, msg, key) { return sign('rsa', alg, msg, pem); }; +rsa.verifyAsync = util.promisify(rsa.verify); +rsa.signAsync = util.promisify(rsa.sign); + /* * ECDSA */ @@ -57,7 +64,7 @@ ecdsa.verify = function verify(curve, msg, alg, key, sig) { assert(curve, 'No curve selected.'); ec = elliptic.ec(curve); - hash = crypto.hash(alg, msg); + hash = backend.hash(alg, msg); return ec.verify(hash, sig, key); }; @@ -68,11 +75,14 @@ ecdsa.sign = function sign(curve, msg, alg, key) { assert(curve, 'No curve selected.'); ec = elliptic.ec(curve); - hash = crypto.hash(alg, msg); + hash = backend.hash(alg, msg); return new Buffer(ec.sign(hash, key)); }; +ecdsa.verifyAsync = util.promisify(ecdsa.verify); +ecdsa.signAsync = util.promisify(ecdsa.sign); + /* * Helpers */ diff --git a/lib/crypto/schnorr.js b/lib/crypto/schnorr.js index 11e6485c..80b80208 100644 --- a/lib/crypto/schnorr.js +++ b/lib/crypto/schnorr.js @@ -10,7 +10,7 @@ var BN = require('bn.js'); var elliptic = require('elliptic'); var Signature = require('elliptic/lib/elliptic/ec/signature'); var hmacDRBG = require('elliptic/lib/elliptic/hmac-drbg'); -var sha256 = require('./crypto').sha256; +var sha256 = require('./backend').sha256; var secp256k1 = elliptic.ec('secp256k1'); var curve = secp256k1.curve; var curves = elliptic.curves; diff --git a/lib/crypto/scrypt-async.js b/lib/crypto/scrypt-async.js index eb0fe710..54a5342f 100644 --- a/lib/crypto/scrypt-async.js +++ b/lib/crypto/scrypt-async.js @@ -33,10 +33,11 @@ 'use strict'; -var util = require('../utils/util'); -var crypto = require('./crypto'); +var co = require('../utils/co'); +var backend = require('./backend'); var native = require('../utils/native').binding; var U32Array = typeof Uint32Array === 'function' ? Uint32Array : Array; +var scryptAsync, smix; /** * Javascript scrypt implementation. Scrypt is @@ -51,35 +52,28 @@ var U32Array = typeof Uint32Array === 'function' ? Uint32Array : Array; * @returns {Buffer} */ -function scryptAsync(passwd, salt, N, r, p, len, callback) { - var V, XY; +scryptAsync = co(function* scryptAsync(passwd, salt, N, r, p, len) { + var i, B, V, XY; if (r * p >= (1 << 30)) - return callback(new Error('EFBIG')); + throw new Error('EFBIG'); if ((N & (N - 1)) !== 0 || N === 0) - return callback(new Error('EINVAL')); + throw new Error('EINVAL'); if (N > 0xffffffff) - return callback(new Error('EINVAL')); + throw new Error('EINVAL'); XY = new Buffer(256 * r); V = new Buffer(128 * r * N); - crypto.pbkdf2Async(passwd, salt, 1, p * 128 * r, 'sha256', function(err, B) { - if (err) - return callback(err); + B = yield backend.pbkdf2Async(passwd, salt, 1, p * 128 * r, 'sha256'); - forRangeSerial(0, p, function(i, next) { - smix(B, i * 128 * r, r, N, V, XY, next); - }, function(err) { - if (err) - return callback(err); + for (i = 0; i < p; i++) + yield smix(B, i * 128 * r, r, N, V, XY); - crypto.pbkdf2Async(passwd, B, 1, len, 'sha256', callback); - }); - }); -} + return yield backend.pbkdf2Async(passwd, B, 1, len, 'sha256'); +}); if (native) scryptAsync = native.scryptAsync; @@ -148,7 +142,7 @@ function R(a, b) { return (a << b) | (a >>> (32 - b)); } -function blockmix_salsa8(B, Y, Yo, r, callback) { +function blockmix_salsa8(B, Y, Yo, r) { var X = new Buffer(64); var i; @@ -165,42 +159,35 @@ function blockmix_salsa8(B, Y, Yo, r, callback) { for (i = 0; i < r; i++) blkcpy(B, Y, (i + r) * 64, Yo + (i * 2 + 1) * 64, 64); - - util.nextTick(callback); } function integerify(B, r) { return B.readUInt32LE((2 * r - 1) * 64, true); } -function smix(B, Bo, r, N, V, XY, callback) { +smix = co(function* smix(B, Bo, r, N, V, XY) { var X = XY; var Y = XY; + var i; var j; blkcpy(X, B, 0, Bo, 128 * r); - forRangeSerial(0, N, function(i, next) { + for (i = 0; i < N; i++) { blkcpy(V, X, i * (128 * r), 0, 128 * r); - blockmix_salsa8(X, Y, 128 * r, r, next); - }, function(err) { - if (err) - return callback(err); + blockmix_salsa8(X, Y, 128 * r, r); + yield co.wait(); + } - forRangeSerial(0, N, function(i, next) { - j = integerify(X, r) & (N - 1); - blkxor(X, V, 0, j * (128 * r), 128 * r); - blockmix_salsa8(X, Y, 128 * r, r, next); - }, function(err) { - if (err) - return callback(err); + for (i = 0; i < N; i++) { + j = integerify(X, r) & (N - 1); + blkxor(X, V, 0, j * (128 * r), 128 * r); + blockmix_salsa8(X, Y, 128 * r, r); + yield co.wait(); + } - blkcpy(B, X, Bo, 0, 128 * r); - - callback(); - }); - }); -} + blkcpy(B, X, Bo, 0, 128 * r); +}); function blkcpy(dest, src, s1, s2, len) { src.copy(dest, s1, s2, s2 + len); @@ -211,17 +198,6 @@ function blkxor(dest, src, s1, s2, len) { dest[s1 + i] ^= src[s2 + i]; } -function forRangeSerial(from, to, iter, callback) { - (function next(err) { - if (err) - return callback(err); - if (from >= to) - return callback(); - from++; - iter(from - 1, next, from - 1); - })(); -} - /* * Expose */ diff --git a/lib/crypto/scrypt.js b/lib/crypto/scrypt.js index d4163185..6c306cad 100644 --- a/lib/crypto/scrypt.js +++ b/lib/crypto/scrypt.js @@ -33,7 +33,7 @@ 'use strict'; -var crypto = require('./crypto'); +var backend = require('./backend'); var native = require('../utils/native').binding; var U32Array = typeof Uint32Array === 'function' ? Uint32Array : Array; @@ -65,12 +65,12 @@ function scrypt(passwd, salt, N, r, p, len) { XY = new Buffer(256 * r); V = new Buffer(128 * r * N); - B = crypto.pbkdf2(passwd, salt, 1, p * 128 * r, 'sha256'); + B = backend.pbkdf2(passwd, salt, 1, p * 128 * r, 'sha256'); for (i = 0; i < p; i++) smix(B, i * 128 * r, r, N, V, XY); - return crypto.pbkdf2(passwd, B, 1, len, 'sha256'); + return backend.pbkdf2(passwd, B, 1, len, 'sha256'); } if (native) diff --git a/lib/hd/mnemonic.js b/lib/hd/mnemonic.js index bca0532e..88c982d4 100644 --- a/lib/hd/mnemonic.js +++ b/lib/hd/mnemonic.js @@ -135,14 +135,19 @@ Mnemonic.prototype.destroy = function destroy() { */ Mnemonic.prototype.toSeed = function toSeed(passphrase) { + var phrase, passwd; + if (!passphrase) passphrase = this.passphrase; this.passphrase = passphrase; + phrase = nfkd(this.getPhrase()); + passwd = nfkd('mnemonic' + passphrase); + return crypto.pbkdf2( - nfkd(this.getPhrase()), - nfkd('mnemonic' + passphrase), + new Buffer(phrase, 'utf8'), + new Buffer(passwd, 'utf8'), 2048, 64, 'sha512'); }; diff --git a/lib/net/bip152.js b/lib/net/bip152.js index 6ceeb13e..f4a90db2 100644 --- a/lib/net/bip152.js +++ b/lib/net/bip152.js @@ -349,7 +349,7 @@ CompactBlock.prototype.fromBlock = function fromBlock(block, witness, nonce) { this.totalTX = block.totalTX; if (!nonce) - nonce = util.nonce(true); + nonce = util.nonce(); this.keyNonce = nonce; diff --git a/lib/net/packets.js b/lib/net/packets.js index 04398fa9..23adad87 100644 --- a/lib/net/packets.js +++ b/lib/net/packets.js @@ -7,7 +7,6 @@ 'use strict'; -var BN = require('bn.js'); var constants = require('../protocol/constants'); var util = require('../utils/util'); var assert = require('assert'); @@ -122,7 +121,7 @@ function VersionPacket(options) { this.ts = time.now(); this.recv = new NetworkAddress(); this.from = new NetworkAddress(); - this.nonce = new BN(0); + this.nonce = constants.ZERO_U64; this.agent = constants.USER_AGENT; this.height = 0; this.relay = true; @@ -196,7 +195,7 @@ VersionPacket.prototype.toRaw = function toRaw(writer) { p.write64(this.ts); this.recv.toRaw(false, p); this.from.toRaw(false, p); - p.writeU64(this.nonce); + p.writeBytes(this.nonce); p.writeVarString(this.agent, 'ascii'); p.write32(this.height); p.writeU8(this.relay ? 1 : 0); @@ -278,7 +277,7 @@ VersionPacket.prototype.fromRaw = function fromRaw(data) { if (p.left() > 0) { this.from.fromRaw(p, false); - this.nonce = p.readU64(); + this.nonce = p.readBytes(8); } if (p.left() > 0) @@ -392,7 +391,7 @@ PingPacket.prototype.toRaw = function toRaw(writer) { var p = BufferWriter(writer); if (this.nonce) - p.writeU64(this.nonce); + p.writeBytes(this.nonce); if (!writer) p = p.render(); @@ -409,7 +408,7 @@ PingPacket.prototype.toRaw = function toRaw(writer) { PingPacket.prototype.fromRaw = function fromRaw(data) { var p = BufferReader(data); if (p.left() >= 8) - this.nonce = p.readU64(); + this.nonce = p.readBytes(8); return this; }; @@ -440,7 +439,7 @@ function PongPacket(nonce) { Packet.call(this); - this.nonce = nonce || new BN(0); + this.nonce = nonce || constants.ZERO_U64; } util.inherits(PongPacket, Packet); @@ -456,7 +455,7 @@ PongPacket.prototype.type = exports.types.PONG; PongPacket.prototype.toRaw = function toRaw(writer) { var p = BufferWriter(writer); - p.writeU64(this.nonce); + p.writeBytes(this.nonce); if (!writer) p = p.render(); @@ -472,7 +471,7 @@ PongPacket.prototype.toRaw = function toRaw(writer) { PongPacket.prototype.fromRaw = function fromRaw(data) { var p = BufferReader(data); - this.nonce = p.readU64(); + this.nonce = p.readBytes(8); return this; }; diff --git a/lib/net/peer.js b/lib/net/peer.js index 62f60cf8..2dee24e1 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -1439,7 +1439,7 @@ Peer.prototype._handleVersion = co(function* _handleVersion(version) { throw new Error('Peer sent a duplicate version.'); if (!this.network.selfConnect) { - if (version.nonce.cmp(this.pool.localNonce) === 0) { + if (util.equal(version.nonce, this.pool.localNonce)) { this.ignore(); throw new Error('We connected to ourself. Oops.'); } @@ -1869,8 +1869,8 @@ Peer.prototype._handlePong = function _handlePong(packet) { return; } - if (nonce.cmp(this.challenge) !== 0) { - if (nonce.cmpn(0) === 0) { + if (!util.equal(nonce, this.challenge)) { + if (util.equal(nonce, constants.ZERO_U64)) { this.logger.debug('Peer sent a zero nonce (%s).', this.hostname); this.challenge = null; return; diff --git a/lib/net/pool.js b/lib/net/pool.js index 03f77bd3..9d63359e 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -917,6 +917,9 @@ Pool.prototype._handleBlock = co(function* _handleBlock(block, peer) { // Fulfill the load request. requested = this.fulfill(block); + if (util.isBrowser) + yield block.hashAsync(); + // Someone is sending us blocks without // us requesting them. if (!requested) { @@ -1330,6 +1333,9 @@ Pool.prototype.hasReject = function hasReject(hash) { Pool.prototype._handleTX = co(function* _handleTX(tx, peer) { var i, requested, missing; + if (util.isBrowser) + yield tx.hashAsync(); + // Fulfill the load request. requested = this.fulfill(tx); diff --git a/lib/primitives/abstractblock.js b/lib/primitives/abstractblock.js index e7044890..82f28e7d 100644 --- a/lib/primitives/abstractblock.js +++ b/lib/primitives/abstractblock.js @@ -10,6 +10,7 @@ var assert = require('assert'); var constants = require('../protocol/constants'); var util = require('../utils/util'); +var co = require('../utils/co'); var crypto = require('../crypto/crypto'); var btcutils = require('../btc/utils'); var VerifyResult = require('../btc/errors').VerifyResult; @@ -160,6 +161,54 @@ AbstractBlock.prototype.hash = function hash(enc) { return hash; }; +/** + * Hash the block headers (async). + * @param {String?} enc - Can be `'hex'` or `null`. + * @returns {Hash|Buffer} hash + */ + +AbstractBlock.prototype.hashAsync = co(function* hashAsync(enc) { + var hash = this._hash; + var hex; + + if (!hash) { + hash = yield crypto.hash256Async(this.abbr()); + if (!this.mutable) + this._hash = hash; + } + + if (enc === 'hex') { + hex = this._hhash; + if (!hex) { + hex = hash.toString('hex'); + if (!this.mutable) + this._hhash = hex; + } + hash = hex; + } + + return hash; +}); + +/** + * Cache hashes. + * @private + */ + +AbstractBlock.prototype.cacheHashes = co(function* cacheHashes() { + var i, tx; + + yield this.hashAsync(); + + if (!this.txs) + return; + + for (i = 0; i < this.txs.length; i++) { + tx = this.txs[i]; + yield tx.hashAsync(); + } +}); + /** * Serialize the block headers. * @returns {Buffer} diff --git a/lib/primitives/memblock.js b/lib/primitives/memblock.js index c4a7e728..faefd321 100644 --- a/lib/primitives/memblock.js +++ b/lib/primitives/memblock.js @@ -78,6 +78,22 @@ MemBlock.fromOptions = function fromOptions(options) { return new MemBlock().fromOptions(options); }; +/** + * Serialize the block headers. + * @returns {Buffer} + */ + +MemBlock.prototype.abbr = function abbr(writer) { + var data = this.raw.slice(0, 80); + + if (writer) { + writer.writeBytes(data); + return writer; + } + + return data; +}; + /** * Get the full block size. * @returns {Number} diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index eba52316..590e7053 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -9,6 +9,7 @@ var assert = require('assert'); var util = require('../utils/util'); +var co = require('../utils/co'); var crypto = require('../crypto/crypto'); var btcutils = require('../btc/utils'); var Amount = require('../btc/amount'); @@ -241,6 +242,35 @@ TX.prototype.hash = function _hash(enc) { return hash; }; +/** + * Hash the transaction with the non-witness serialization (async). + * @param {String?} enc - Can be `'hex'` or `null`. + * @returns {Hash|Buffer} hash + */ + +TX.prototype.hashAsync = co(function* _hashAsync(enc) { + var hash = this._hash; + var hex; + + if (!hash) { + hash = yield crypto.hash256Async(this.toNormal()); + if (!this.mutable) + this._hash = hash; + } + + if (enc === 'hex') { + hex = this._hhash; + if (!hex) { + hex = hash.toString('hex'); + if (!this.mutable) + this._hhash = hex; + } + hash = hex; + } + + return hash; +}); + /** * Hash the transaction with the witness * serialization, return the wtxid (normal diff --git a/lib/protocol/constants.js b/lib/protocol/constants.js index 56ecb2e6..40e924b2 100644 --- a/lib/protocol/constants.js +++ b/lib/protocol/constants.js @@ -698,6 +698,22 @@ exports.ZERO_SIG64 = new Buffer('' 'hex' ); +/** + * 4 zero bytes. + * @const {Buffer} + * @default + */ + +exports.ZERO_U32 = new Buffer('00000000', 'hex'); + +/** + * 8 zero bytes. + * @const {Buffer} + * @default + */ + +exports.ZERO_U64 = new Buffer('0000000000000000', 'hex'); + /** * BCoin version. * @const {String} diff --git a/lib/utils/reader.js b/lib/utils/reader.js index 90faa1e8..54595746 100644 --- a/lib/utils/reader.js +++ b/lib/utils/reader.js @@ -595,7 +595,7 @@ BufferReader.prototype.readNullString = function readNullString(enc) { BufferReader.prototype.createChecksum = function createChecksum() { var start = this.stack[this.stack.length - 1] || 0; var data = this.data.slice(start, this.offset); - return crypto.checksum(data).readUInt32LE(0, true); + return crypto.hash256(data).readUInt32LE(0, true); }; /** diff --git a/lib/utils/util.js b/lib/utils/util.js index a6b0ea53..4b226f93 100644 --- a/lib/utils/util.js +++ b/lib/utils/util.js @@ -13,7 +13,6 @@ var assert = require('assert'); var nodeUtil = require('util'); var fs = require('fs'); var os = require('os'); -var BN = require('bn.js'); var util = exports; var Number, Math, Date; @@ -542,38 +541,14 @@ util.time = function time(date) { /** * Create a 64 bit nonce. - * @returns {BN} + * @returns {Buffer} */ -util.nonce = function _nonce(buffer) { +util.nonce = function _nonce() { var nonce = new Buffer(8); - nonce.writeUInt32LE((Math.random() * 0x100000000) >>> 0, 0, true); nonce.writeUInt32LE((Math.random() * 0x100000000) >>> 0, 4, true); - - if (buffer) - return nonce; - - return new BN(nonce); -}; - -/** - * Test whether a buffer is all zeroes. - * @param {Buffer} data - * @returns {Boolean} - */ - -util.isZero = function isZero(data) { - var i; - - assert(Buffer.isBuffer(data)); - - for (i = 0; i < data.length; i++) { - if (data[i] !== 0) - return false; - } - - return true; + return nonce; }; /** @@ -1015,3 +990,21 @@ util._paths = {}; util.fastProp = function fastProp(obj) { ({ __proto__: obj }); }; + +/** + * Promisify a function. + * @param {Function} func + * @returns {Function} + */ + +util.promisify = function promisify(func) { + return function() { + var result; + try { + result = func.apply(this, arguments); + } catch (e) { + return Promise.reject(e); + } + return Promise.resolve(result); + }; +}; diff --git a/lib/utils/writer.js b/lib/utils/writer.js index 41f9d8b3..3ab86687 100644 --- a/lib/utils/writer.js +++ b/lib/utils/writer.js @@ -105,7 +105,7 @@ BufferWriter.prototype.render = function render(keep) { case BYTES: off += item[1].copy(data, off); break; case STR: off += data.write(item[1], off, item[2]); break; case CHECKSUM: - off += crypto.checksum(data.slice(0, off)).copy(data, off); + off += crypto.hash256(data.slice(0, off)).copy(data, off, 0, 4); break; case FILL: data.fill(item[1], off, off + item[2]); diff --git a/lib/wallet/masterkey.js b/lib/wallet/masterkey.js index c70dfab6..efbed89a 100644 --- a/lib/wallet/masterkey.js +++ b/lib/wallet/masterkey.js @@ -15,6 +15,12 @@ var BufferReader = require('../utils/reader'); var BufferWriter = require('../utils/writer'); var HD = require('../hd/hd'); +/* + * Constants + */ + +var BCOIN_SALT = new Buffer('bcoin', 'ascii'); + /** * Master BIP32 key which can exist * in a timed out encrypted state. @@ -234,11 +240,14 @@ MasterKey.prototype.stop = function stop() { */ MasterKey.prototype.derive = function derive(passwd) { + if (typeof passwd === 'string') + passwd = new Buffer(passwd, 'utf8'); + switch (this.alg) { case MasterKey.alg.PBKDF2: - return crypto.pbkdf2Async(passwd, 'bcoin', this.N, 32, 'sha256'); + return crypto.pbkdf2Async(passwd, BCOIN_SALT, this.N, 32, 'sha256'); case MasterKey.alg.SCRYPT: - return crypto.scryptAsync(passwd, 'bcoin', this.N, this.r, this.p, 32); + return crypto.scryptAsync(passwd, BCOIN_SALT, this.N, this.r, this.p, 32); default: return Promise.reject(new Error('Unknown algorithm: ' + this.alg)); } diff --git a/package.json b/package.json index cda31bb1..e1bb5905 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "elliptic": "6.3.2" }, "optionalDependencies": { - "bcoin-native": "0.0.9", + "bcoin-native": "0.0.10", "leveldown": "1.5.0", "secp256k1": "3.2.0", "socket.io": "1.4.8", diff --git a/test/aes-test.js b/test/aes-test.js index 8a0d23c5..3924b9b3 100644 --- a/test/aes-test.js +++ b/test/aes-test.js @@ -58,6 +58,38 @@ describe('AES', function() { ]); } + function bencrypt(data, passphrase) { + var key, cipher; + + assert(nativeCrypto, 'No crypto module available.'); + assert(passphrase, 'No passphrase.'); + + if (typeof data === 'string') + data = new Buffer(data, 'utf8'); + + if (typeof passphrase === 'string') + passphrase = new Buffer(passphrase, 'utf8'); + + key = pbkdf2key(passphrase, 2048, 32, 16); + return crypto.encipher(data, key.key, key.iv); + } + + function bdecrypt(data, passphrase) { + var key, decipher; + + assert(nativeCrypto, 'No crypto module available.'); + assert(passphrase, 'No passphrase.'); + + if (typeof data === 'string') + data = new Buffer(data, 'hex'); + + if (typeof passphrase === 'string') + passphrase = new Buffer(passphrase, 'utf8'); + + key = pbkdf2key(passphrase, 2048, 32, 16); + return crypto.decipher(data, key.key, key.iv); + } + function encrypt(data, passphrase) { var key, cipher; @@ -101,9 +133,14 @@ describe('AES', function() { var enchash2 = nencrypt(hash2, 'foo'); var dechash2 = ndecrypt(enchash2, 'foo'); + var hash3 = crypto.sha256(new Buffer([])); + var enchash3 = bencrypt(hash3, 'foo'); + var dechash3 = bdecrypt(enchash3, 'foo'); + assert.deepEqual(hash, hash2); assert.deepEqual(enchash, enchash2); assert.deepEqual(dechash, dechash2); + assert.deepEqual(dechash, dechash3); }); it('should encrypt and decrypt a hash with uneven blocks', function() { diff --git a/test/scrypt-test.js b/test/scrypt-test.js index 7de5c701..158f45cd 100644 --- a/test/scrypt-test.js +++ b/test/scrypt-test.js @@ -5,7 +5,9 @@ var scrypt = require('../lib/crypto/crypto').scrypt; describe('Scrypt', function() { it('should perform scrypt with N=16', function() { - var result = scrypt('', '', 16, 1, 1, 64); + var pass = new Buffer(''); + var salt = new Buffer(''); + var result = scrypt(pass, salt, 16, 1, 1, 64); assert.equal(result.toString('hex'), '' + '77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3f' + 'ede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628' @@ -13,7 +15,9 @@ describe('Scrypt', function() { }); it('should perform scrypt with N=1024', function() { - var result = scrypt('password', 'NaCl', 1024, 8, 16, 64); + var pass = new Buffer('password'); + var salt = new Buffer('NaCl'); + var result = scrypt(pass, salt, 1024, 8, 16, 64); assert.equal(result.toString('hex'), '' + 'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e773' + '76634b3731622eaf30d92e22a3886ff109279d9830dac727afb9' @@ -21,7 +25,9 @@ describe('Scrypt', function() { }); it('should perform scrypt with N=16384', function() { - var result = scrypt('pleaseletmein', 'SodiumChloride', 16384, 8, 1, 64); + var pass = new Buffer('pleaseletmein'); + var salt = new Buffer('SodiumChloride'); + var result = scrypt(pass, salt, 16384, 8, 1, 64); assert.equal(result.toString('hex'), '' + '7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b54' + '3f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d' @@ -30,7 +36,9 @@ describe('Scrypt', function() { // Only enable if you want to wait a while. // it('should perform scrypt with N=1048576', function() { - // var result = scrypt('pleaseletmein', 'SodiumChloride', 1048576, 8, 1, 64); + // var pass = new Buffer('pleaseletmein'); + // var salt = new Buffer('SodiumChloride'); + // var result = scrypt(pass, salt, 1048576, 8, 1, 64); // assert.equal(result.toString('hex'), '' // + '2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5' // + 'ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049'