UNPKG

ecdh

Version:

Native node.js module for ECDH and ECDSA

322 lines (245 loc) 7.89 kB
var crypto = require("crypto"), BigInteger = require("jsbn").BigInteger, ECPointFp = require("./jsbn/ec.js").ECPointFp; /*** Static functions ***/ exports.BigInteger = BigInteger; exports.Curves = require("./jsbn/sec.js"); exports.getCurve = function (name) { if (!exports.Curves[name]) throw new Error("Curve `" + name + "` is not supported"); return exports.Curves[name](); }; exports.getBytesLength = function (curve) { return Math.ceil(curve.getN().bitLength() * 0.125); }; exports.generateR = function (curve, callback) { var n = curve.getN(); return crypto.randomBytes(n.bitLength(), callback); }; exports.generateKeys = function (curve, r) { var privateKey = PrivateKey.generate(curve, r); return { publicKey: privateKey.derivePublicKey(), privateKey: privateKey, }; }; /*** PublicKey class ***/ //var HEADER_XY = 0x04, // HEADER_X_EVEN = 0x02, // HEADER_X_ODD = 0x03, var PublicKey = (exports.PublicKey = function (curve, Q, buf) { this.curve = curve; this.Q = Q; if (buf) { this.buffer = buf; } else { var bytes = exports.getBytesLength(curve), size = bytes * 2; this.buffer = Buffer.allocUnsafe(size); fillBuffer( this.Q.getX().toBigInteger().toString(16), bytes, this.buffer, 0 ); fillBuffer( this.Q.getY().toBigInteger().toString(16), bytes, this.buffer, bytes ); } }); PublicKey.fromBuffer = function (curve, buf) { var bytes = exports.getBytesLength(curve), size = bytes * 2; if (buf.length !== size) throw new Error("Invaild buffer length"); var x = buf.slice(0, bytes), // skip the 04 for uncompressed format y = buf.slice(bytes), c = curve.getCurve(), P = new ECPointFp( c, c.fromBigInteger(new BigInteger(x.toString("hex"), 16)), c.fromBigInteger(new BigInteger(y.toString("hex"), 16)) ); return new PublicKey(curve, P, buf); }; //PublicKey.compress = function() { // // this will work only on U curve // // var x = this.Q.getX().toBigInteger(), // y = this.Q.getY().toBigInteger(); // // var xBa = hexToBuffer(x.toString(16), 'hex'), // buf = Buffer.allocUnsafe(xBa.length+1); // // if (y.isEven()) // buf[0] = HEADER_X_EVEN; // else // buf[0] = HEADER_X_ODD; // // xBa.copy(buf, 1); // return buf; //}; PublicKey.prototype.verifySignature = function (hash, signature) { var data = deserializeSig(signature), r = data.r, s = data.s, Q = this.Q, e = new BigInteger(hash.toString("hex"), 16), n = this.curve.getN(), G = this.curve.getG(); if (r.compareTo(BigInteger.ONE) < 0 || r.compareTo(n) >= 0) return false; if (s.compareTo(BigInteger.ONE) < 0 || s.compareTo(n) >= 0) return false; var c = s.modInverse(n), u1 = e.multiply(c).mod(n), u2 = r.multiply(c).mod(n), // TODO we may want to use Shamir's trick here: point = G.multiply(u1).add(Q.multiply(u2)), v = point.getX().toBigInteger().mod(n); return v.equals(r); }; /*** PrivateKey class ***/ var PrivateKey = (exports.PrivateKey = function (curve, key, buf) { this.curve = curve; this.d = key; if (buf) { this.buffer = buf; this._size = buf.length; } else { this._size = exports.getBytesLength(curve); this.buffer = zeroBuffer(key.toString(16), this._size); } }); PrivateKey.generate = function (curve, r) { r = new BigInteger(r || exports.generateR(curve)); var n1 = curve.getN().subtract(BigInteger.ONE), priv = r.mod(n1).add(BigInteger.ONE); return new PrivateKey(curve, priv); }; PrivateKey.fromBuffer = function (curve, buf) { var size = exports.getBytesLength(curve); if (buf.length !== size) throw new Error("Invaild buffer length"); var key = new BigInteger(buf.toString("hex"), 16); return new PrivateKey(curve, key, buf); }; PrivateKey.prototype.derivePublicKey = function () { var P = this.curve.getG().multiply(this.d); return new PublicKey(this.curve, P); }; PrivateKey.prototype.onCurve = function (publicKey) { var x = publicKey.Q.getX().x, y = publicKey.Q.getY().x, a = this.curve.curve.a.x, b = this.curve.curve.b.x, q = this.curve.curve.q; if (x.compareTo(BigInteger.ZERO) < 0 || x.compareTo(q) >= 0) return false; if (y.compareTo(BigInteger.ZERO) < 0 || y.compareTo(q) >= 0) return false; var left = y.pow(2).mod(q), right = x.pow(3).add(a.multiply(x)).add(b).mod(q); if (left.compareTo(right) == 0) return true; else return false; }; PrivateKey.prototype.deriveSharedSecret = function (publicKey) { if (!publicKey || !publicKey.Q || !this.onCurve(publicKey)) throw new Error("publicKey is invaild"); var S = publicKey.Q.multiply(this.d); return zeroBuffer(S.getX().toBigInteger().toString(16), this._size); }; PrivateKey.prototype.sign = function (hash, algorithm) { if (!hash || !hash.length) throw new Error("hash is invaild"); if (!algorithm) throw new Error("hash algorithm is required"); var n = this.curve.getN(), e = new BigInteger(hash.toString("hex"), 16), length = exports.getBytesLength(this.curve); do { var k = deterministicGenerateK(hash, this.buffer, algorithm, length), G = this.curve.getG(), Q = G.multiply(k), r = Q.getX().toBigInteger().mod(n); } while (r.compareTo(BigInteger.ZERO) <= 0); var s = k .modInverse(n) .multiply(e.add(this.d.multiply(r))) .mod(n); return serializeSig(r, s); }; /*** local helpers ***/ var DER_SEQUENCE = 0x30, DER_INTEGER = 0x02; function hexToBuffer(hex) { if (hex.length % 2 === 1) hex = "0" + hex; return Buffer.allocUnsafe(hex, "hex"); } function zeroBuffer(hex, bytes) { return fillBuffer(hex, bytes, Buffer.allocUnsafe(bytes), 0); } function fillBuffer(hex, bytes, buf, start) { if (hex.length % 2 === 1) hex = "0" + hex; var length = hex.length * 0.5, pos = start + bytes - length; buf.fill(0, start, pos); buf.write(hex, pos, length, "hex"); return buf; } // generate K value based on RFC6979 function deterministicGenerateK(hash, key, algorithm, length) { var v = Buffer.allocUnsafe(length), k = Buffer.allocUnsafe(length); v.fill(1); k.fill(0); var hmac = crypto.createHmac(algorithm, k); hmac.update(v); hmac.update(Buffer.allocUnsafe([0])); hmac.update(key); hmac.update(hash); k = hmac.digest(); hmac = crypto.createHmac(algorithm, k); hmac.update(v); v = hmac.digest(); hmac = crypto.createHmac(algorithm, k); hmac.update(v); hmac.update(Buffer.allocUnsafe([1])); hmac.update(key); hmac.update(hash); k = hmac.digest(); hmac = crypto.createHmac(algorithm, k); hmac.update(v); v = hmac.digest(); hmac = crypto.createHmac(algorithm, k); hmac.update(v); v = hmac.digest(); return new BigInteger(v.toString("hex"), 16); } function serializeSig(r, s) { var rBa = hexToBuffer(r.toString(16), "hex"); var sBa = hexToBuffer(s.toString(16), "hex"); var buf = Buffer.allocUnsafe(6 + rBa.length + sBa.length), end = buf.length - sBa.length; buf[0] = DER_SEQUENCE; buf[1] = buf.length - 2; buf[2] = DER_INTEGER; buf[3] = rBa.length; rBa.copy(buf, 4); buf[end - 2] = DER_INTEGER; buf[end - 1] = sBa.length; sBa.copy(buf, end); return buf; } function deserializeSig(buf) { if (buf[0] !== DER_SEQUENCE) throw new Error("Signature is not a valid DERSequence"); if (buf[1] > buf.length - 2) throw new Error("Signature length is too short"); if (buf[2] !== DER_INTEGER) throw new Error("First element in signature must be a DERInteger"); var pos = 4, rBa = buf.slice(pos, pos + buf[3]); pos += rBa.length; if (buf[pos++] !== DER_INTEGER) throw new Error("Second element in signature must be a DERInteger"); var sBa = buf.slice(pos + 1, pos + 1 + buf[pos]); return { r: new BigInteger(rBa.toString("hex"), 16), s: new BigInteger(sBa.toString("hex"), 16), }; }