From 4e1001a84244ac90738624e0e81ad7cd8eafd5df Mon Sep 17 00:00:00 2001 From: Kagami Hiiragi Date: Tue, 13 Jan 2015 22:39:37 +0300 Subject: [PATCH] ECDH (Node) --- .gitignore | 1 + .npmignore | 7 ++-- README.md | 20 +++++++--- binding.gyp | 61 ++++++++++++++++++++++++++++++ ecdh.cc | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++ index.js | 16 ++++++++ package.json | 4 ++ test.js | 6 +-- 8 files changed, 205 insertions(+), 12 deletions(-) create mode 100644 binding.gyp create mode 100644 ecdh.cc diff --git a/.gitignore b/.gitignore index 0c0aa04..fd8ea13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /node_modules/ /npm-debug.log +/build/ diff --git a/.npmignore b/.npmignore index 19b6835..cc20228 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,4 @@ -.travis.yml -.jshint* -karma.conf.js +/.travis.yml +/.jshint* +/karma.conf.js +/build/ diff --git a/README.md b/README.md index 5d7acee..2f05c4a 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,7 @@ JavaScript Elliptic curve cryptography library for both browserify and node. ## Motivation -There is currently no any isomorphic ECC library which provides ECDSA, ECDH and ECIES for both Node.js and Browser and uses the fastest libraries available (e.g. [secp256k1-node](https://github.com/wanderer/secp256k1-node) is much faster than other libraries but can be used only on Node.js). So `eccrypto` is an attempt to create one. Current goals: - -- [x] ~~Convert private key to public~~ -- [x] ~~ECDSA~~ -- [ ] ECDH -- [ ] ECIES +There is currently no any isomorphic ECC library which provides ECDSA, ECDH and ECIES for both Node.js and Browser and uses the fastest implementation available (e.g. [secp256k1-node](https://github.com/wanderer/secp256k1-node) is much faster than other libraries but can be used only on Node.js). So `eccrypto` is an attempt to create one. ## Implementation details @@ -61,6 +56,19 @@ eccrypto.sign(privateKey, msg).then(function(sig) { ### ECDH ```js +var crypto = require("crypto"); +var eccrypto = require("eccrypto"); + +var privateKeyA = crypto.randomBytes(32); +var publicKeyA = eccrypto.getPublic(privateKeyA); +var privateKeyB = crypto.randomBytes(32); +var publicKeyB = eccrypto.getPublic(privateKeyB); + +eccrypto.derive(privateKeyA, publicKeyB).then(function(sharedKey1) { + eccrypto.derive(privateKeyB, publicKeyA).then(function(sharedKey2) { + console.log("Both shared keys are equal:", sharedKey1, sharedKey2); + }); +}); ``` ### ECIES diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 0000000..05fde38 --- /dev/null +++ b/binding.gyp @@ -0,0 +1,61 @@ +{ + "targets": [ + { + "target_name": "ecdh", + "include_dirs": [" +#include +#include +#include + +#define PRIVKEY_SIZE 32 +#define PUBKEY_SIZE 65 +#define CHECK(cond) do { if (!(cond)) goto error; } while (0) + +using node::Buffer; +using v8::Handle; +using v8::FunctionTemplate; +using v8::Object; +using v8::String; + +int derive(const uint8_t* privkey_a, const uint8_t* pubkey_b, uint8_t* shared) { + int rc = -1; + int res; + BIGNUM* pkey_bn = NULL; + BIGNUM* peerkey_bn_x = NULL; + BIGNUM* peerkey_bn_y = NULL; + EC_KEY* pkey = NULL; + EC_KEY* peerkey = NULL; + EVP_PKEY* evp_pkey = NULL; + EVP_PKEY* evp_peerkey = NULL; + EVP_PKEY_CTX* ctx = NULL; + size_t shared_len = PRIVKEY_SIZE; + + // Private key A. + CHECK((pkey_bn = BN_bin2bn(privkey_a, PRIVKEY_SIZE, NULL)) != NULL); + CHECK((pkey = EC_KEY_new_by_curve_name(NID_secp256k1)) != NULL); + CHECK(EC_KEY_set_private_key(pkey, pkey_bn) == 1); + CHECK((evp_pkey = EVP_PKEY_new()) != NULL); + CHECK(EVP_PKEY_set1_EC_KEY(evp_pkey, pkey) == 1); + + // Public key B. + CHECK((peerkey_bn_x = BN_bin2bn(pubkey_b+1, PRIVKEY_SIZE, NULL)) != NULL); + CHECK((peerkey_bn_y = BN_bin2bn(pubkey_b+33, PRIVKEY_SIZE, NULL)) != NULL); + CHECK((peerkey = EC_KEY_new_by_curve_name(NID_secp256k1)) != NULL); + res = EC_KEY_set_public_key_affine_coordinates(peerkey, + peerkey_bn_x, + peerkey_bn_y); + CHECK(res == 1); + CHECK((evp_peerkey = EVP_PKEY_new()) != NULL); + CHECK(EVP_PKEY_set1_EC_KEY(evp_peerkey, peerkey) == 1); + + // Derive shared secret. + CHECK((ctx = EVP_PKEY_CTX_new(evp_pkey, NULL)) != NULL); + CHECK(EVP_PKEY_derive_init(ctx) == 1); + CHECK(EVP_PKEY_derive_set_peer(ctx, evp_peerkey) == 1); + CHECK((EVP_PKEY_derive(ctx, shared, &shared_len)) == 1); + CHECK(shared_len == PRIVKEY_SIZE); + + rc = 0; +error: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(evp_peerkey); + EC_KEY_free(peerkey); + BN_free(peerkey_bn_y); + BN_free(peerkey_bn_x); + EVP_PKEY_free(evp_pkey); + EC_KEY_free(pkey); + BN_free(pkey_bn); + return rc; +} + +NAN_METHOD(Derive) { + NanScope(); + + if (args.Length() != 2 || + !args[0]->IsObject() || // privkey_a + !args[1]->IsObject()) { // pubkey_b + return NanThrowError("Bad input"); + } + + char* privkey_a = Buffer::Data(args[0]->ToObject()); + size_t privkey_a_len = Buffer::Length(args[0]->ToObject()); + char* pubkey_b = Buffer::Data(args[1]->ToObject()); + size_t pubkey_b_len = Buffer::Length(args[1]->ToObject()); + if (privkey_a == NULL || + privkey_a_len != PRIVKEY_SIZE || + pubkey_b == NULL || + pubkey_b_len != PUBKEY_SIZE || + pubkey_b[0] != 4) { + return NanThrowError("Bad input"); + } + + uint8_t* shared = (uint8_t *)malloc(PRIVKEY_SIZE); + if (derive((uint8_t *)privkey_a, (uint8_t *)pubkey_b, shared)) { + free(shared); + return NanThrowError("Internal error"); + } + NanReturnValue(NanBufferUse((char *)shared, PRIVKEY_SIZE)); +} + +void InitAll(Handle exports) { + exports->Set( + NanNew("derive"), + NanNew(Derive)->GetFunction()); +} + +NODE_MODULE(ecdh, InitAll) diff --git a/index.js b/index.js index c3ece93..2dce2e0 100644 --- a/index.js +++ b/index.js @@ -8,7 +8,10 @@ var promise = typeof Promise === "undefined" ? require("es6-promise").Promise : Promise; +// TODO(Kagami): We may fallback to pure JS implementation +// (`browser.js`) if this modules are failed to load. var secp256k1 = require("secp256k1"); +var ecdh = require("./build/Release/ecdh"); /** * Compute the public key for a given private key. @@ -44,3 +47,16 @@ exports.verify = function(publicKey, msg, sig) { return secp256k1.verify(publicKey, msg, sig) === 1 ? resolve() : reject(); }); }; + +/** + * Derive shared secret for given private and public keys. + * @param {Buffer} privateKeyA - Sender's private key + * @param {Buffer} publicKeyB - Recipient's public key + * @return {Promise.} A promise that resolves with the derived + * shared secret (Px) and rejects on bad key. + */ +exports.derive = function(privateKeyA, publicKeyB) { + return new promise(function(resolve) { + resolve(ecdh.derive(privateKeyA, publicKeyB)); + }); +}; diff --git a/package.json b/package.json index c83a22c..7a4bcdb 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "index.js", "browser": "browser.js", "scripts": { + "install": "node-gyp rebuild || exit 0", "test": "mocha && xvfb-run -a karma start && jshint .", "m": "mocha", "k": "xvfb-run -a karma start", @@ -49,6 +50,9 @@ "dependencies": { "elliptic": "^1.0.1", "es6-promise": "^2.0.1", + "nan": "^1.4.1" + }, + "optionalDependencies": { "secp256k1": "~0.0.13" } } diff --git a/test.js b/test.js index 3f4161b..2098cb9 100644 --- a/test.js +++ b/test.js @@ -81,8 +81,6 @@ describe("ECDSA", function() { }); }); -if (typeof window !== "undefined") { - describe("ECDH", function() { it("should derive shared secret from privkey A and pubkey B", function() { return eccrypto.derive(privateKeyA, publicKeyB).then(function(Px) { @@ -92,7 +90,7 @@ describe("ECDH", function() { return eccrypto.derive(privateKeyB, publicKeyA).then(function(Px2) { expect(Buffer.isBuffer(Px2)).to.be.true; expect(Px2.length).to.equal(32); - expect(Px.toString("hex")).to.equal(Px2.toString("hex")); + expect(bufferEqual(Px, Px2)).to.be.true; }); }); }); @@ -110,6 +108,8 @@ describe("ECDH", function() { }); }); +if (typeof window !== "undefined") { + describe("ECIES", function() { var ephemPrivateKey = Buffer(32); ephemPrivateKey.fill(4);