Skip to content

Instantly share code, notes, and snippets.

@jonasfj
Last active July 13, 2018 01:06
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonasfj/af453cc2c569312ac59f to your computer and use it in GitHub Desktop.
Save jonasfj/af453cc2c569312ac59f to your computer and use it in GitHub Desktop.
Comparison of tweetnacl, tweetnacl-fast, node-sodium and js-nacl for node.js

Tests and Benchmarks for Node Ed25519 libraries and bindings

Mainly looking at using this for signing HTTP(S) requests, as a replacements for HMAC-SHA256, which requires symmetric keys.

Note, we want to this work in browser, python and node. But we only really care about performance in node. Clients that sign request, usually don't make hundreds of these per second. And python servers are slow anyways :)

So if it's fast server side on node, when deployed correctly (ie. without falling back to a JS implementation), then that would be great.

TODO:

  • Test compatibility with python libraries
  • Consider detached signature support for node-sodium, already implemented in this fork.

Done:

  • Added fromSeed to node-sodium (PR pending)
  • Ensure node 0.11 support: node-sodium ported (PR-pending)
var crypto = require('crypto');
var assert = require('assert');
var Benchmark = require('benchmark');
var tweetnacl = require('tweetnacl');
var tweetnaclfast = require('tweetnacl/nacl-fast');
var sodium = require('sodium');
var jsnacl = require('js-nacl');
var base64_to_Uint8Array = function(input) {
var raw = new Buffer(input, 'base64');
var arr = new Uint8Array(new ArrayBuffer(raw.length));
for(i = 0; i < raw.length; i++) {
arr[i] = raw[i];
}
return arr;
};
var string_to_Uint8Array = function(str) {
var raw = new Buffer(str, 'utf8');
var arr = new Uint8Array(new ArrayBuffer(raw.length));
for(i = 0; i < raw.length; i++) {
arr[i] = raw[i];
}
return arr;
};
var _seed = 'Aav6yqemxoPNNqxeKJXMlruKxXEHLD931S8pXzxt4mk=';
var _pubkey = 'DsWygyoTcB7/NT5OqRzT0eaFf+6bJBSSBRfDOyU3x9k=';
var _message = "Hi, this is a string that I want signed, will keep it short";
var _sig = 'IvmJ8ntaMtcoVaU3lDToeQPdG/CdL7an4r013gYgJbY+eXJiwVZ9IxU/OC5htH41x2ezZRd83rTwe2+jf1f3CQ==';
/*********************** TweetNaCl tests ***************************/
var test_tweetnacl = function(nacl) {
this.nacl = nacl;
this._seed = base64_to_Uint8Array(_seed);
this.key = null;
this._pubkey = base64_to_Uint8Array(_pubkey);
this._message = string_to_Uint8Array(_message);
this.sig = null;
this._sig = base64_to_Uint8Array(_sig);
};
test_tweetnacl.prototype.fromSeed = function() {
this.key = this.nacl.sign.keyPair.fromSeed(this._seed);
};
test_tweetnacl.prototype.sign = function() {
this.sig = this.nacl.sign.detached(this._message, this.key.secretKey);
};
test_tweetnacl.prototype.verify = function() {
var r = this.nacl.sign.detached.verify(this._message, this.sig, this._pubkey);
assert(r, "Verification failed!");
};
test_tweetnacl.prototype.validate = function() {
assert(new Buffer(this.key.publicKey).toString('base64') === _pubkey, "wrong public key");
assert(new Buffer(this.sig).toString('base64') === _sig, "wrong signature");
};
/*********************** Sodium tests ***************************/
var test_sodium = function() {
this._seed = base64_to_Uint8Array(_seed);
this.key = null;
};
test_sodium.prototype.fromSeed = function() {
this.key = new sodium.Key.Sign.fromSeed(_seed, 'base64');
};
test_sodium.prototype.sign = function() {
// Detached signatures: https://github.com/paixaop/node-sodium/issues/22
var signer = new sodium.Sign(this.key);
var sig = signer.sign(_message, 'utf8');
this.sig = sig.sign.slice(0, 64).toString('base64');
};
test_sodium.prototype.verify = function() {
var input = {
sign: Buffer.concat([
new Buffer(this.sig, 'base64'),
new Buffer(_message, 'utf8')
]),
publicKey: new Buffer(_pubkey, 'base64')
};
var r = sodium.Sign.verify(input);
assert(r, "Verification failed!");
};
test_sodium.prototype.validate = function() {
assert(new Buffer(this.key.pk().get()).toString('base64') === _pubkey, "wrong public key");
assert(this.sig === _sig, "wrong signature");
};
/*********************** js-NaCl tests ***************************/
var test_jsnacl = function() {
this.nacl = jsnacl.instantiate();
this._seed = base64_to_Uint8Array(_seed);
this.key = null;
this._pubkey = base64_to_Uint8Array(_pubkey);
this._message = string_to_Uint8Array(_message);
this.sig = null;
this._sig = base64_to_Uint8Array(_sig);
};
test_jsnacl.prototype.fromSeed = function() {
this.key = this.nacl.crypto_sign_keypair_from_seed(this._seed);
};
test_jsnacl.prototype.sign = function() {
this.sig = this.nacl.crypto_sign_detached(this._message, this.key.signSk);
};
test_jsnacl.prototype.verify = function() {
var r = this.nacl.crypto_sign_verify_detached(this.sig, this._message, this.key.signPk);
assert(r, "Verification failed!");
};
test_jsnacl.prototype.validate = function() {
assert(new Buffer(this.key.signPk).toString('base64') === _pubkey, "wrong public key");
assert(new Buffer(this.sig).toString('base64') === _sig, "wrong signature");
};
/*********************** Actual tests ***************************/
console.log("Correctness Testing:");
console.log(" - testing sodium");
var sod = new test_sodium();
sod.fromSeed();
sod.sign();
sod.verify();
sod.validate();
console.log(" - testing js-NaCl");
var jsn = new test_jsnacl();
jsn.fromSeed();
jsn.sign();
jsn.verify();
jsn.validate();
console.log(" - testing tweetnacl");
var tw1 = new test_tweetnacl(tweetnacl);
tw1.fromSeed();
tw1.sign();
tw1.verify();
tw1.validate();
console.log(" - testing tweetnacl-fast");
var tw2 = new test_tweetnacl(tweetnaclfast);
tw2.fromSeed();
tw2.sign();
tw2.verify();
tw2.validate();
/*********************** Benchmark HMAC-256 ***************************/
console.log("\nBrenchmark HMAC (from crypto):");
new Benchmark.Suite()
.add('HMAC-256 (crypto)', function() {
crypto.createHmac('SHA256', _pubkey).update(_message).digest('base64');
})
.add('HMAC-512 (crypto)', function() {
crypto.createHmac('SHA512', _pubkey).update(_message).digest('base64');
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.run();
/*********************** Benchmark FromSeed ***************************/
console.log("\nBrenchmark FromSeed:");
new Benchmark.Suite()
.add('sodium.fromSeed', function() {
sod.fromSeed();
})
.add('js-NaCl.fromSeed', function() {
jsn.fromSeed();
})
.add('tweetnacl.fromSeed', function() {
tw1.fromSeed();
})
.add('tweetnacl-fast.fromSeed', function() {
tw2.fromSeed();
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
})
.run();
/*********************** Benchmark sign ***************************/
console.log("\nBrenchmark Sign:");
new Benchmark.Suite()
.add('sodium.sign', function() {
sod.sign();
})
.add('js-NaCl.sign', function() {
jsn.sign();
})
.add('tweetnacl.sign', function() {
tw1.sign();
})
.add('tweetnacl-fast.sign', function() {
tw2.sign();
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
})
.run();
/*********************** Benchmark verify ***************************/
console.log("\nBrenchmark Verify:");
new Benchmark.Suite()
.add('sodium.verify', function() {
sod.verify();
})
.add('js-NaCl.verify', function() {
jsn.verify();
})
.add('tweetnacl.verify', function() {
tw1.verify();
})
.add('tweetnacl-fast.verify', function() {
tw2.verify();
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
})
.run();
console.log("\nDone");
{
"name": "ecdsa-experiments",
"version": "0.0.0",
"private": true,
"dependencies": {
"promise": "6.0.1",
"lodash": "2.4.1",
"debug": "2.1.0",
"ecdsa": "0.6.0",
"eckey": "",
"elliptic": "0.15.14",
"asn1.js": "0.6.4",
"secp256k1": "",
"tweetnacl": "",
"js-nacl": "",
"sodium": "jonasfj/node-sodium#sign-seed-keypair",
"benchmark": "",
"microtime": ""
},
"engines": {
"node": "0.10.x"
}
}
Correctness Testing:
- testing sodium
- testing js-NaCl
- testing tweetnacl
- testing tweetnacl-fast
Brenchmark HMAC (from crypto):
HMAC-256 (crypto) x 124,634 ops/sec ±1.62% (94 runs sampled)
HMAC-512 (crypto) x 117,018 ops/sec ±1.19% (98 runs sampled)
Brenchmark FromSeed:
sodium.fromSeed x 4,374 ops/sec ±2.46% (86 runs sampled)
js-NaCl.fromSeed x 89.20 ops/sec ±0.30% (78 runs sampled)
tweetnacl.fromSeed x 10.88 ops/sec ±2.90% (32 runs sampled)
tweetnacl-fast.fromSeed x 23.24 ops/sec ±2.32% (43 runs sampled)
Fastest is sodium.fromSeed
Brenchmark Sign:
sodium.sign x 9,635 ops/sec ±1.56% (92 runs sampled)
js-NaCl.sign x 85.47 ops/sec ±1.05% (76 runs sampled)
tweetnacl.sign x 10.87 ops/sec ±2.87% (32 runs sampled)
tweetnacl-fast.sign x 22.83 ops/sec ±2.74% (43 runs sampled)
Fastest is sodium.sign
Brenchmark Verify:
sodium.verify x 5,972 ops/sec ±0.69% (98 runs sampled)
js-NaCl.verify x 26.24 ops/sec ±0.41% (48 runs sampled)
tweetnacl.verify x 5.48 ops/sec ±2.10% (18 runs sampled)
tweetnacl-fast.verify x 11.58 ops/sec ±2.67% (33 runs sampled)
Fastest is sodium.verify
Done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment