Skip to content

Commit

Permalink
ECDH (Node)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kagami committed Jan 13, 2015
1 parent cd217b2 commit 4e1001a
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 12 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/node_modules/
/npm-debug.log
/build/
7 changes: 4 additions & 3 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.travis.yml
.jshint*
karma.conf.js
/.travis.yml
/.jshint*
/karma.conf.js
/build/
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
61 changes: 61 additions & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"targets": [
{
"target_name": "ecdh",
"include_dirs": ["<!(node -e \"require('nan')\")"],
"cflags": ["-Wall", "-O2"],
"sources": ["ecdh.cc"],
"conditions": [
["OS=='win'", {
"conditions": [
[
"target_arch=='x64'", {
"variables": {
"openssl_root%": "C:/OpenSSL-Win64"
},
}, {
"variables": {
"openssl_root%": "C:/OpenSSL-Win32"
}
}
]
],
"libraries": [
"-l<(openssl_root)/lib/libeay32.lib",
],
"include_dirs": [
"<(openssl_root)/include",
],
}, {
"conditions": [
[
"target_arch=='ia32'", {
"variables": {
"openssl_config_path": "<(nodedir)/deps/openssl/config/piii"
}
}
],
[
"target_arch=='x64'", {
"variables": {
"openssl_config_path": "<(nodedir)/deps/openssl/config/k8"
},
}
],
[
"target_arch=='arm'", {
"variables": {
"openssl_config_path": "<(nodedir)/deps/openssl/config/arm"
}
}
],
],
"include_dirs": [
"<(nodedir)/deps/openssl/openssl/include",
"<(openssl_config_path)"
]
}
]]
}
]
}
102 changes: 102 additions & 0 deletions ecdh.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#include <node.h>
#include <nan.h>
#include <openssl/evp.h>
#include <openssl/ec.h>

#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<Object> exports) {
exports->Set(
NanNew<String>("derive"),
NanNew<FunctionTemplate>(Derive)->GetFunction());
}

NODE_MODULE(ecdh, InitAll)
16 changes: 16 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.<Buffer>} 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));
});
};
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -49,6 +50,9 @@
"dependencies": {
"elliptic": "^1.0.1",
"es6-promise": "^2.0.1",
"nan": "^1.4.1"
},
"optionalDependencies": {
"secp256k1": "~0.0.13"
}
}
6 changes: 3 additions & 3 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
});
});
});
Expand All @@ -110,6 +108,8 @@ describe("ECDH", function() {
});
});

if (typeof window !== "undefined") {

describe("ECIES", function() {
var ephemPrivateKey = Buffer(32);
ephemPrivateKey.fill(4);
Expand Down

0 comments on commit 4e1001a

Please sign in to comment.