diff --git a/ellipticcurve/curve.js b/ellipticcurve/curve.js index 2817c0d..2bd718b 100644 --- a/ellipticcurve/curve.js +++ b/ellipticcurve/curve.js @@ -7,6 +7,7 @@ const BigInt = require("big-integer"); const Point = require("./point").Point; const modulo = require("./utils/integer").modulo; +const EcdsaMath = require("./math"); class CurveFp { @@ -41,6 +42,18 @@ class CurveFp { get oid() { return this._oid.slice(); } + + y(x, isEven) + { + let ySquared = (((x.value ** BigInt(3).value) % this.P.value) + this.A.value * x.value + this.B.value) % this.P.value + let y = EcdsaMath.modularSquareRoot(ySquared, this.P.value) + if (isEven != ((y % BigInt(2).value).toString() == "0")) + { + y = this.P.value - y; + } + return BigInt(y) + } + }; diff --git a/ellipticcurve/math.js b/ellipticcurve/math.js index a85fe7d..cde9c82 100644 --- a/ellipticcurve/math.js +++ b/ellipticcurve/math.js @@ -2,6 +2,20 @@ const Point = require("./point").Point; const modulo = require("./utils/integer").modulo; const BigInt = require("big-integer"); +var modularSquareRoot = function(value, prime){ + + let base = value + let exp = (prime + BigInt(1).value) / BigInt(4).value + let p = prime + + var result = 1n; + while (exp !== 0n) { + if (exp % 2n === 1n) result = result * base % p; + base = base * base % p; + exp >>= 1n; + } + return result; +} var multiply = function (p, n, N, A, P) { // Fast way to multily point and scalar in elliptic curves @@ -184,7 +198,7 @@ var jacobianMultiply = function (p, n, N, A, P) { throw new Error("logical failure: p: " + p + ", n: " + n + ", N: " + N + ", A: " + A + ", P: " + P); }; - +exports.modularSquareRoot = modularSquareRoot; exports.multiply = multiply; exports.add = add; exports.inv = inv; diff --git a/ellipticcurve/publicKey.js b/ellipticcurve/publicKey.js index e19f7db..548966f 100644 --- a/ellipticcurve/publicKey.js +++ b/ellipticcurve/publicKey.js @@ -3,6 +3,7 @@ const EcdsaCurve = require("./curve"); const Point = require("./point").Point; const der = require("./utils/der"); const Math = require("./math"); +const { PrivateKey } = require(".."); class PublicKey { @@ -21,6 +22,19 @@ class PublicKey { return xString + yString; }; + toCompressed(encoded=false) { + let baseLen = this.curve.length(); + + if ((this.point.y["value"] % BigInt("2")).toString() == "0") + { + var parityTag = "02"; + } else { + var parityTag = "03"; + } + let xString = BinaryAscii.hexFromBinary(BinaryAscii.stringFromNumber(this.point.x, baseLen)) + return parityTag + xString + } + toDer () { let encodeEcAndOid = der.encodeSequence(der.encodeOid([1, 2, 840, 10045, 2, 1]), der.encodeOid(this.curve.oid)); @@ -102,6 +116,21 @@ class PublicKey { } return publicKey }; + + static fromCompressed(string, curve=EcdsaCurve.secp256k1) + { + let parityTag = string.slice(0, 2); + let xString = string.slice(2, string.length); + + if (!["02", "03"].includes(parityTag)) + { + throw new Error("Compressed string should start with 02 or 03"); + } + let isEven = parityTag == "02" + let x = BinaryAscii.numberFromHex(xString) + let y = curve.y(x, isEven=isEven) + return new PublicKey(new Point(x, y), curve) + }; }; diff --git a/test/test.js b/test/test.js index 0cebcdd..27378d1 100644 --- a/test/test.js +++ b/test/test.js @@ -137,6 +137,58 @@ describe("PublicKey test", function() { }); }); }); + +describe("ComPubKeyTest", function() { + describe("#testBatch()", function() { + it("should validate publicKey x and y points", function() { + this.timeout(10000); + for (let i = 0; i < 1000; i++) { + let privateKey = new PrivateKey() + let publicKey = privateKey.publicKey() + let publicKeyString = publicKey.toCompressed() + let recoveredPublicKey = PublicKey.fromCompressed(publicKeyString, publicKey.curve) + + assert.equal(publicKey.point.x.value, recoveredPublicKey.point.x.value) + assert.equal(publicKey.point.y.value, recoveredPublicKey.point.y.value) + } + }); + }); + describe("#testFromCompressedEven()", function() { + it("should validate publicKey from Even compressed", function() { + let publicKeyCompressed = "0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2" + let publicKey = PublicKey.fromCompressed(publicKeyCompressed) + let publicKey2 = PublicKey.fromPem("\n-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEUpclctRl0BbUxQGIe43zA+7j7WAsBWse\nsJJg36DaCrKIdC9NyX2e22/ZRrq8AC/fsG8myvEXuUBe15J1dj/bHA==\n-----END PUBLIC KEY-----\n") + + assert.equal(publicKey.toPem(), publicKey2.toPem()); + }); + }); + describe("#testFromCompressedOdd()", function() { + it("should validate publicKey from Odd compressed", function() { + let publicKeyCompressed = "0318ed2e1ec629e2d3dae7be1103d4f911c24e0c80e70038f5eb5548245c475f50" + let publicKey = PublicKey.fromCompressed(publicKeyCompressed) + let publicKey2 = PublicKey.fromPem("\n-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEGO0uHsYp4tPa574RA9T5EcJODIDnADj1\n61VIJFxHX1BMIg0B4cpBnLG6SzOTthXpndIKpr8HEHj3D9lJAI50EQ==\n-----END PUBLIC KEY-----\n") + + assert.equal(publicKey.toPem(), publicKey2.toPem()); + }); + }); + describe("#testToCompressedEven()", function() { + it("should validate publicKey to Even compressed", function() { + let publicKey = PublicKey.fromPem("\n-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEUpclctRl0BbUxQGIe43zA+7j7WAsBWse\nsJJg36DaCrKIdC9NyX2e22/ZRrq8AC/fsG8myvEXuUBe15J1dj/bHA==\n-----END PUBLIC KEY-----\n") + let publicKeyCompressed = publicKey.toCompressed() + + assert.equal(publicKeyCompressed, "0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2"); + }); + }); + describe("#testToCompressedOdd()", function() { + it("should validate publicKey to Odd compressed", function() { + let publicKey = PublicKey.fromPem("-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEGO0uHsYp4tPa574RA9T5EcJODIDnADj1\n61VIJFxHX1BMIg0B4cpBnLG6SzOTthXpndIKpr8HEHj3D9lJAI50EQ==\n-----END PUBLIC KEY-----") + let publicKeyCompressed = publicKey.toCompressed() + + assert.equal(publicKeyCompressed, "0318ed2e1ec629e2d3dae7be1103d4f911c24e0c80e70038f5eb5548245c475f50"); + }); + }); +}); + describe("Signature test", function() { describe("#testDerConversion()", function() { it("should validate DER signature generation and convertion", function() {