From 6ddd0775537a0eb9bd45df8a7ce556ab4e668f43 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Sat, 25 Nov 2017 16:13:11 +0200 Subject: [PATCH 1/5] Create benchmark script for Node --- examples/bench.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ 2 files changed, 71 insertions(+) create mode 100644 examples/bench.js diff --git a/examples/bench.js b/examples/bench.js new file mode 100644 index 0000000..9ba78d6 --- /dev/null +++ b/examples/bench.js @@ -0,0 +1,69 @@ +'use strict'; + +const bench = require('benchmark'); +const pwbox = require('..'); +const sodiumPwbox = pwbox.withCrypto('libsodium'); + +// Tested message lengths +const dataLengths = [ + 32, 64, 128, 256, 512, 1024, 4096, 16384, 65536 +]; +const password = 'correct horse battery staple'; +// Interval (in seconds) between bench cycles +const testDelay = 0.15; + +const tweetnaclSuite = new bench.Suite('pwbox.withCrypto(\'tweetnacl\')'); +const libsodiumSuite = new bench.Suite('pwbox.withCrypto(\'libsodium\')'); + +dataLengths.forEach(len => { + const data = new Uint8Array(len); + for (let i = 0; i < data.length; i++) { + data[i] = Math.floor(Math.random() * 256); + } + + tweetnaclSuite.add(tweetnaclSuite.name + ', message.length = ' + len, { + defer: true, + delay: testDelay, + fn: (deferred) => { + pwbox(data, password).then(box => { + if (!(box instanceof Uint8Array) || box.length !== len + pwbox.overheadLength) { + throw new Error('Invalid box returned'); + } + deferred.resolve(); + }); + } + }); + + libsodiumSuite.add(libsodiumSuite.name + ', message.length = ' + len, { + defer: true, + delay: 0.15, + fn: (deferred) => { + sodiumPwbox(data, password).then(box => { + if (!(box instanceof Uint8Array) || box.length !== len + pwbox.overheadLength) { + throw new Error('Invalid box returned'); + } + deferred.resolve(); + }); + } + }); +}); + +function runSuites (suites, events) { + if (suites.length === 0) { + events.complete(); + return; + } + + const suite = suites.shift(); + suite + .on('cycle', events.cycle) + .on('complete', () => runSuites(suites, events)) + .run(); +} + +runSuites([tweetnaclSuite, libsodiumSuite], { + cycle (event) { + console.log(event.target.toString()); + }, + complete () {} +}); diff --git a/package.json b/package.json index df1560e..5db0610 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "test-demo": "mocha test/test.demo.js", "lint": "semistandard", "browser": "mkdirp dist && browserify lite.js -t [ babelify --presets [ es2015 ] ] --standalone pwbox | uglifyjs -o dist/pwbox-lite.min.js", + "bench": "node examples/bench.js", "prepublish": "npm run browser" }, "keywords": [ @@ -46,6 +47,7 @@ "babel-plugin-istanbul": "^4.1.4", "babel-preset-es2015": "^6.24.1", "babelify": "^7.3.0", + "benchmark": "^2.1.4", "browserify": "^14.3.0", "chai": "^3.5.0", "chai-as-promised": "^6.0.0", From 3a041c12020e54f3074ea48f5d882408948ad2ea Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Sat, 25 Nov 2017 16:18:55 +0200 Subject: [PATCH 2/5] Add browser-based benchmark --- examples/bench.html | 251 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 examples/bench.html diff --git a/examples/bench.html b/examples/bench.html new file mode 100644 index 0000000..b2dfdc3 --- /dev/null +++ b/examples/bench.html @@ -0,0 +1,251 @@ + + + + + + pwbox Benchmarks + + + + +
+
+

pwbox

+

pwbox is a library similar to NaCl/libsodium's built-in secretbox, only it implements + encryption based on passwords rather on secret keys.

+
+
+ +
+

Benchmark + + +

+ +

This benchmark tests pwbox encryption speed on messages with different sizes, from 32B to 64kB. On desktops, the latency of tests should be order of 100 ms; on mobile platforms – order of 1,000 ms.

+ +
+
+
+
+
+
 
+
+
+
+
+
+
+
+ +
+ © 2017 Exonum Team +
+ GitHub • + Npm +
+
+
+ + + + + + + + + + + + + From 1ffd48f9c7328e96f04408bdafde45e0b95adab9 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 7 Dec 2017 14:39:23 +0200 Subject: [PATCH 3/5] Fix nitpicks with bench script - Make script executable - Add missing message sizes --- examples/bench.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/examples/bench.js b/examples/bench.js index 9ba78d6..233d669 100644 --- a/examples/bench.js +++ b/examples/bench.js @@ -1,3 +1,21 @@ +#!/usr/bin/env node + +/** + * @license + * Copyright 2017 The Exonum Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ 'use strict'; const bench = require('benchmark'); @@ -6,7 +24,7 @@ const sodiumPwbox = pwbox.withCrypto('libsodium'); // Tested message lengths const dataLengths = [ - 32, 64, 128, 256, 512, 1024, 4096, 16384, 65536 + 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536 ]; const password = 'correct horse battery staple'; // Interval (in seconds) between bench cycles From ec29ee06e32b4dd9b40d3d2924767e6651fea07f Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 7 Dec 2017 14:40:39 +0200 Subject: [PATCH 4/5] Allow to customize params for Node benchmark --- examples/bench.js | 142 ++++++++++++++++++++++++++++++++++------------ package.json | 1 + 2 files changed, 108 insertions(+), 35 deletions(-) diff --git a/examples/bench.js b/examples/bench.js index 233d669..803fa70 100644 --- a/examples/bench.js +++ b/examples/bench.js @@ -18,53 +18,60 @@ */ 'use strict'; +const program = require('commander'); const bench = require('benchmark'); const pwbox = require('..'); +const manifest = require('../package.json'); const sodiumPwbox = pwbox.withCrypto('libsodium'); // Tested message lengths -const dataLengths = [ +const DEFAULT_SIZES = [ 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536 ]; -const password = 'correct horse battery staple'; -// Interval (in seconds) between bench cycles -const testDelay = 0.15; -const tweetnaclSuite = new bench.Suite('pwbox.withCrypto(\'tweetnacl\')'); -const libsodiumSuite = new bench.Suite('pwbox.withCrypto(\'libsodium\')'); +function createSuites (messageSizes, pwboxOptions) { + const password = 'correct horse battery staple'; + // Interval (in seconds) between bench cycles + const testDelay = 0.15; -dataLengths.forEach(len => { - const data = new Uint8Array(len); - for (let i = 0; i < data.length; i++) { - data[i] = Math.floor(Math.random() * 256); - } + const tweetnaclSuite = new bench.Suite('pwbox.withCrypto(\'tweetnacl\')'); + const libsodiumSuite = new bench.Suite('pwbox.withCrypto(\'libsodium\')'); - tweetnaclSuite.add(tweetnaclSuite.name + ', message.length = ' + len, { - defer: true, - delay: testDelay, - fn: (deferred) => { - pwbox(data, password).then(box => { - if (!(box instanceof Uint8Array) || box.length !== len + pwbox.overheadLength) { - throw new Error('Invalid box returned'); - } - deferred.resolve(); - }); + messageSizes.forEach(len => { + const data = new Uint8Array(len); + for (let i = 0; i < data.length; i++) { + data[i] = Math.floor(Math.random() * 256); } - }); - libsodiumSuite.add(libsodiumSuite.name + ', message.length = ' + len, { - defer: true, - delay: 0.15, - fn: (deferred) => { - sodiumPwbox(data, password).then(box => { - if (!(box instanceof Uint8Array) || box.length !== len + pwbox.overheadLength) { - throw new Error('Invalid box returned'); - } - deferred.resolve(); - }); - } + tweetnaclSuite.add(tweetnaclSuite.name + ', message.length = ' + len, { + defer: true, + delay: testDelay, + fn: (deferred) => { + pwbox(data, password, pwboxOptions).then(box => { + if (!(box instanceof Uint8Array) || box.length !== len + pwbox.overheadLength) { + throw new Error('Invalid box returned'); + } + deferred.resolve(); + }); + } + }); + + libsodiumSuite.add(libsodiumSuite.name + ', message.length = ' + len, { + defer: true, + delay: 0.05, + fn: (deferred) => { + sodiumPwbox(data, password, pwboxOptions).then(box => { + if (!(box instanceof Uint8Array) || box.length !== len + pwbox.overheadLength) { + throw new Error('Invalid box returned'); + } + deferred.resolve(); + }); + } + }); }); -}); + + return [tweetnaclSuite, libsodiumSuite]; +} function runSuites (suites, events) { if (suites.length === 0) { @@ -79,7 +86,72 @@ function runSuites (suites, events) { .run(); } -runSuites([tweetnaclSuite, libsodiumSuite], { +/** + * Parses an integer with an optional metric suffix 'k', 'M', or 'G' + * (case-insensitive). + * + * @param {string} number + * @returns {?number} + */ +function parseSuffixedNumber (number) { + const matches = number.toUpperCase().match(/^(\d+)(K|M|G)?$/); + if (!matches) return undefined; + + let value = parseInt(matches[1]); + const suffix = matches[2]; + switch (suffix) { + case 'K': value *= 1024; break; + case 'M': value *= 1024 * 1024; break; + case 'G': value *= 1024 * 1024 * 1024; break; + } + return value; +} + +program + .version(manifest.version) + .arguments('[sizes]') + .description('Benchmark pwbox performance.\n\n' + + ' [sizes] is an optional comma-separated list of message sizes\n' + + ' to benchmark. Numbers in sizes and options may be appended with "k", "M"\n' + + ' or "G" suffixes to multiply the number by 2**10, 2**20, or\n' + + ' 2**30 respectively.') + .option('-o --opslimit ', 'Operations limit for pwbox', parseSuffixedNumber) + .option('-m --memlimit ', 'Memory limit for pwbox', parseSuffixedNumber) + .on('--help', () => { + console.log(''); + console.log(' Example:'); + console.log(''); + console.log(' $ bench.js -o 512k --memlimit 16M 1k,4k,16k,1M'); + console.log(''); + }) + .parse(process.argv); + +const messageSizes = program.args.length === 0 + ? DEFAULT_SIZES + : program.args[0].split(',').map(parseSuffixedNumber); + +program.opslimit = program.opslimit || pwbox.defaultOpslimit; +program.memlimit = program.memlimit || pwbox.defaultMemlimit; + +if ( + typeof program.opslimit !== 'number' || + typeof program.memlimit !== 'number' || + !messageSizes.every(size => size > 0) +) { + program.outputHelp(); + process.exit(1); +} + +const pwboxOptions = { + opslimit: program.opslimit, + memlimit: program.memlimit +}; +console.log('pwbox options:', pwboxOptions); +console.log('Message sizes:', messageSizes); +console.log(''); + +const suites = createSuites(messageSizes, pwboxOptions); +runSuites(suites, { cycle (event) { console.log(event.target.toString()); }, diff --git a/package.json b/package.json index 5db0610..918a7cc 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "chai": "^3.5.0", "chai-as-promised": "^6.0.0", "chai-bytes": "^0.1.1", + "commander": "^2.12.2", "coveralls": "^3.0.0", "dirty-chai": "^1.2.2", "istanbul": "^0.4.5", From b113b6be05d950b21d91df3fb1e9eba28a889a61 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 7 Dec 2017 14:40:59 +0200 Subject: [PATCH 5/5] Mention benchmarks in README --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 88b25d7..317420f 100644 --- a/README.md +++ b/README.md @@ -140,8 +140,11 @@ pwbox(message, password, { The default values for `opslimit` and `memlimit` are also taken from libsodium (`524288` and `16777216`, respectively). With the default parameters, `pwbox` uses 16 MB of RAM and completes -with a comfortable 100ms delay in Node, several hundred ms in browsers +with a comfortable 100 ms delay in Node and in browsers on desktops/laptops, and under a second on smartphones. +(The package contains [a benchmarking script for Node](examples/bench.js) +and [the page for browser benchmarking](examples/bench.html), so you can measure +**pwbox** performance yourself.) You may use increased parameter values for better security; see the [crypto spec](doc/cryptography.md#parameter-validation) for more details.