@nlindley/eccrypto
Version:
JavaScript Elliptic curve cryptography library
600 lines (491 loc) • 21.3 kB
JavaScript
const chai = require("chai");
const chaiAsPromised = require("chai-as-promised");
const createHash = require("crypto").createHash;
const bufferEqual = require("buffer-equal");
const eccrypto = require("./");
const { expect } = chai;
chai.use(chaiAsPromised);
const msg = createHash("sha256").update("test").digest();
const otherMsg = createHash("sha256").update("test2").digest();
const shortMsg = createHash("sha1").update("test").digest();
const privateKey = Buffer.alloc(32);
privateKey.fill(1);
const publicKey = eccrypto.getPublic(privateKey);
const publicKeyCompressed = eccrypto.getPublicCompressed(privateKey);
const privateKeyA = Buffer.alloc(32);
privateKeyA.fill(2);
const publicKeyA = eccrypto.getPublic(privateKeyA);
const publicKeyACompressed = eccrypto.getPublicCompressed(privateKeyA);
const privateKeyB = Buffer.alloc(32);
privateKeyB.fill(3);
const publicKeyB = eccrypto.getPublic(privateKeyB);
const publicKeyBCompressed = eccrypto.getPublicCompressed(privateKeyB);
describe("Key conversion", () => {
it("should allow to convert private key to public", () => {
expect(Buffer.isBuffer(publicKey)).to.be.true;
expect(publicKey.toString("hex")).to.equal(
"041b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f70beaf8f588b541507fed6a642c5ab42dfdf8120a7f639de5122d47a69a8e8d1"
);
});
it("should allow to convert private key to compressed public", () => {
expect(Buffer.isBuffer(publicKeyCompressed)).to.be.true;
expect(publicKeyCompressed.toString("hex")).to.equal(
"031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"
);
});
it("should throw on invalid private key", () => {
expect(eccrypto.getPublic.bind(null, Buffer.from("00", "hex"))).to.throw(
Error
);
expect(eccrypto.getPublic.bind(null, Buffer.from("test"))).to.throw(Error);
});
});
describe("ECDSA", () => {
it("should allow to sign and verify message", async () => {
const sig = await eccrypto.sign(privateKey, msg);
expect(Buffer.isBuffer(sig)).to.be.true;
expect(sig.toString("hex")).to.equal(
"3044022078c15897a34de6566a0d396fdef660698c59fef56d34ee36bef14ad89ee0f6f8022016e02e8b7285d93feafafbe745702f142973a77d5c2fa6293596357e17b3b47c"
);
await eccrypto.verify(publicKey, msg, sig);
});
it("should allow to sign and verify message using a compressed public key", async () => {
const sig = await eccrypto.sign(privateKey, msg);
expect(Buffer.isBuffer(sig)).to.be.true;
expect(sig.toString("hex")).to.equal(
"3044022078c15897a34de6566a0d396fdef660698c59fef56d34ee36bef14ad89ee0f6f8022016e02e8b7285d93feafafbe745702f142973a77d5c2fa6293596357e17b3b47c"
);
await eccrypto.verify(publicKeyCompressed, msg, sig);
});
it("shouldn't verify incorrect signature", async () => {
const sig = await eccrypto.sign(privateKey, msg);
expect(Buffer.isBuffer(sig)).to.be.true;
await expect(eccrypto.verify(publicKey, otherMsg, sig)).to.eventually.be
.rejected;
});
it("should reject promise on invalid key when signing", async () => {
const k4 = Buffer.from("test");
const k192 = Buffer.from(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"hex"
);
const k384 = Buffer.from(
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"hex"
);
await expect(eccrypto.sign(k4, msg)).to.eventually.be.rejected;
await expect(eccrypto.sign(k192, msg)).to.eventually.be.rejected;
await expect(eccrypto.sign(k384, msg)).to.eventually.be.rejected;
});
it("should reject promise on invalid key when verifying", async () => {
const sig = await eccrypto.sign(privateKey, msg);
expect(Buffer.isBuffer(sig)).to.be.true;
await expect(eccrypto.verify(Buffer.from("test"), msg, sig)).to.eventually
.be.rejected;
const badKey = Buffer.alloc(65);
publicKey.copy(badKey);
badKey[0] ^= 1;
await expect(eccrypto.verify(badKey, msg, sig)).to.eventually.be.rejected;
});
it("should reject promise on invalid sig when verifying", async () => {
const sig = await eccrypto.sign(privateKey, msg);
expect(Buffer.isBuffer(sig)).to.be.true;
sig[0] ^= 1;
await expect(eccrypto.verify(publicKey, msg, sig)).to.eventually.be
.rejected;
});
it("should allow to sign and verify messages less than 32 bytes", async () => {
const sig = await eccrypto.sign(privateKey, shortMsg);
expect(Buffer.isBuffer(sig)).to.be.true;
expect(sig.toString("hex")).to.equal(
"304402204737396b697e5a3400e3aedd203d8be89879f97708647252bd0c17752ff4c8f302201d52ef234de82ce0719679fa220334c83b80e21b8505a781d32d94a27d9310aa"
);
await eccrypto.verify(publicKey, shortMsg, sig);
});
it("shouldn't sign and verify messages longer than 32 bytes", async () => {
const longMsg = Buffer.alloc(40);
const someSig = Buffer.from(
"304402204737396b697e5a3400e3aedd203d8be89879f97708647252bd0c17752ff4c8f302201d52ef234de82ce0719679fa220334c83b80e21b8505a781d32d94a27d9310aa",
"hex"
);
await expect(eccrypto.sign(privateKey, longMsg)).to.eventually.be.rejected;
await expect(
eccrypto.verify(privateKey, longMsg, someSig)
).to.eventually.be.rejectedWith("Message is too long");
});
it("shouldn't sign and verify empty messages", async () => {
const emptyMsg = Buffer.alloc(0);
const someSig = Buffer.from(
"304402204737396b697e5a3400e3aedd203d8be89879f97708647252bd0c17752ff4c8f302201d52ef234de82ce0719679fa220334c83b80e21b8505a781d32d94a27d9310aa",
"hex"
);
await expect(eccrypto.sign(privateKey, emptyMsg)).to.eventually.be.rejected;
await expect(
eccrypto.verify(publicKey, emptyMsg, someSig)
).to.eventually.be.rejectedWith("Message should not be empty");
});
});
describe("ECDSA Sync", () => {
it("should allow to sign and verify message", () => {
const sig = eccrypto.signSync(privateKey, msg);
expect(Buffer.isBuffer(sig)).to.be.true;
expect(sig.toString("hex")).to.equal(
"3044022078c15897a34de6566a0d396fdef660698c59fef56d34ee36bef14ad89ee0f6f8022016e02e8b7285d93feafafbe745702f142973a77d5c2fa6293596357e17b3b47c"
);
eccrypto.verify(publicKey, msg, sig);
});
it("should allow to sign and verify message using a compressed public key", () => {
const sig = eccrypto.signSync(privateKey, msg);
expect(Buffer.isBuffer(sig)).to.be.true;
expect(sig.toString("hex")).to.equal(
"3044022078c15897a34de6566a0d396fdef660698c59fef56d34ee36bef14ad89ee0f6f8022016e02e8b7285d93feafafbe745702f142973a77d5c2fa6293596357e17b3b47c"
);
eccrypto.verify(publicKeyCompressed, msg, sig);
});
it("shouldn't verify incorrect signature", () => {
const sig = eccrypto.signSync(privateKey, msg);
expect(Buffer.isBuffer(sig)).to.be.true;
expect(() => eccrypto.verifySync(publicKey, otherMsg, sig)).to.throw;
});
it("should throw on invalid key when signing", () => {
const k4 = Buffer.from("test");
const k192 = Buffer.from(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"hex"
);
const k384 = Buffer.from(
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"hex"
);
expect(() => eccrypto.signSync(k4, msg)).to.throw;
expect(() => eccrypto.signSync(k192, msg)).to.throw;
expect(() => eccrypto.signSync(k384, msg)).to.throw;
});
it("should reject promise on invalid key when verifying", () => {
const sig = eccrypto.signSync(privateKey, msg);
expect(Buffer.isBuffer(sig)).to.be.true;
expect(() => eccrypto.verifySync(Buffer.from("test"), msg, sig)).to.throw;
const badKey = Buffer.alloc(65);
publicKey.copy(badKey);
badKey[0] ^= 1;
expect(() => eccrypto.verifySync(badKey, msg, sig)).to.throw;
});
it("should throw on invalid sig when verifying", () => {
const sig = eccrypto.signSync(privateKey, msg);
expect(Buffer.isBuffer(sig)).to.be.true;
sig[0] ^= 1;
expect(() => eccrypto.verifySync(publicKey, msg, sig)).to.throw;
});
it("should allow to sign and verify messages less than 32 bytes", () => {
const sig = eccrypto.signSync(privateKey, shortMsg);
expect(Buffer.isBuffer(sig)).to.be.true;
expect(sig.toString("hex")).to.equal(
"304402204737396b697e5a3400e3aedd203d8be89879f97708647252bd0c17752ff4c8f302201d52ef234de82ce0719679fa220334c83b80e21b8505a781d32d94a27d9310aa"
);
eccrypto.verifySync(publicKey, shortMsg, sig);
});
it("shouldn't sign and verify messages longer than 32 bytes", () => {
const longMsg = Buffer.alloc(40);
const someSig = Buffer.from(
"304402204737396b697e5a3400e3aedd203d8be89879f97708647252bd0c17752ff4c8f302201d52ef234de82ce0719679fa220334c83b80e21b8505a781d32d94a27d9310aa",
"hex"
);
expect(() => eccrypto.signSync(privateKey, longMsg)).to.throw;
expect(() => eccrypto.verifySync(privateKey, longMsg, someSig)).to.throw(
"Message is too long"
);
});
it("shouldn't sign and verify empty messages", () => {
const emptyMsg = Buffer.alloc(0);
const someSig = Buffer.from(
"304402204737396b697e5a3400e3aedd203d8be89879f97708647252bd0c17752ff4c8f302201d52ef234de82ce0719679fa220334c83b80e21b8505a781d32d94a27d9310aa",
"hex"
);
expect(() => eccrypto.signSync(privateKey, emptyMsg)).to.throw;
expect(() => eccrypto.verifySync(publicKey, emptyMsg, someSig)).to.throw(
"Message should not be empty"
);
});
});
describe("ECDH", () => {
it("should derive shared secret from privkey A and pubkey B", async () => {
const Px = await eccrypto.derive(privateKeyA, publicKeyB);
expect(Buffer.isBuffer(Px)).to.be.true;
expect(Px.length).to.equal(32);
expect(Px.toString("hex")).to.equal(
"aca78f27d5f23b2e7254a0bb8df128e7c0f922d47ccac72814501e07b7291886"
);
const Px2 = await eccrypto.derive(privateKeyB, publicKeyA);
expect(Buffer.isBuffer(Px2)).to.be.true;
expect(Px2.length).to.equal(32);
expect(bufferEqual(Px, Px2)).to.be.true;
});
it("should derive shared secret from privkey A and compressed pubkey B", async () => {
const Px = await eccrypto.derive(privateKeyA, publicKeyBCompressed);
expect(Buffer.isBuffer(Px)).to.be.true;
expect(Px.length).to.equal(32);
expect(Px.toString("hex")).to.equal(
"aca78f27d5f23b2e7254a0bb8df128e7c0f922d47ccac72814501e07b7291886"
);
const Px2 = await eccrypto.derive(privateKeyB, publicKeyA);
expect(Buffer.isBuffer(Px2)).to.be.true;
expect(Px2.length).to.equal(32);
expect(bufferEqual(Px, Px2)).to.be.true;
});
it("should reject promise on bad keys", async () => {
await expect(eccrypto.derive(Buffer.from("test"), publicKeyB)).to.eventually
.be.rejected;
await expect(eccrypto.derive(publicKeyB, publicKeyB)).to.eventually.be
.rejected;
await expect(eccrypto.derive(privateKeyA, privateKeyA)).to.eventually.be
.rejected;
await expect(eccrypto.derive(privateKeyB, Buffer.from("test"))).to
.eventually.be.rejected;
});
it("should reject promise on bad arguments", async () => {
await expect(eccrypto.derive({}, {})).to.eventually.be.rejectedWith(
/Bad private key/i
);
});
});
describe("ECDH Sync ", () => {
it("should derive shared secret from privkey A and pubkey B", () => {
const Px = eccrypto.deriveSync(privateKeyA, publicKeyB);
expect(Buffer.isBuffer(Px)).to.be.true;
expect(Px.length).to.equal(32);
expect(Px.toString("hex")).to.equal(
"aca78f27d5f23b2e7254a0bb8df128e7c0f922d47ccac72814501e07b7291886"
);
const Px2 = eccrypto.deriveSync(privateKeyB, publicKeyA);
expect(Buffer.isBuffer(Px2)).to.be.true;
expect(Px2.length).to.equal(32);
expect(bufferEqual(Px, Px2)).to.be.true;
});
it("should derive shared secret from privkey A and compressed pubkey B", () => {
const Px = eccrypto.deriveSync(privateKeyA, publicKeyBCompressed);
expect(Buffer.isBuffer(Px)).to.be.true;
expect(Px.length).to.equal(32);
expect(Px.toString("hex")).to.equal(
"aca78f27d5f23b2e7254a0bb8df128e7c0f922d47ccac72814501e07b7291886"
);
const Px2 = eccrypto.deriveSync(privateKeyB, publicKeyA);
expect(Buffer.isBuffer(Px2)).to.be.true;
expect(Px2.length).to.equal(32);
expect(bufferEqual(Px, Px2)).to.be.true;
});
it("should throw on bad keys", () => {
expect(() => eccrypto.deriveSync(Buffer.from("test"), publicKeyB)).to.throw;
expect(() => eccrypto.deriveSync(publicKeyB, publicKeyB)).to.throw;
expect(() => eccrypto.deriveSync(privateKeyA, privateKeyA)).to.throw;
expect(() => eccrypto.deriveSync(privateKeyB, Buffer.from("test"))).to
.throw;
});
it("should throw on bad arguments", () => {
expect(() => eccrypto.deriveSync({}, {})).to.throw(/Bad private key/i);
});
});
describe("ECIES", () => {
const ephemPrivateKey = Buffer.alloc(32);
ephemPrivateKey.fill(4);
const ephemPublicKey = eccrypto.getPublic(ephemPrivateKey);
const iv = Buffer.alloc(16);
iv.fill(5);
const ciphertext = Buffer.from("bbf3f0e7486b552b0e2ba9c4ca8c4579", "hex");
const mac = Buffer.from(
"dbb14a9b53dbd6b763dba24dc99520f570cdf8095a8571db4bf501b535fda1ed",
"hex"
);
const encOpts = { ephemPrivateKey: ephemPrivateKey, iv: iv };
const decOpts = {
iv: iv,
ephemPublicKey: ephemPublicKey,
ciphertext: ciphertext,
mac: mac,
};
it("should encrypt", async () => {
const enc = await eccrypto.encrypt(
publicKeyB,
Buffer.from("test"),
encOpts
);
expect(bufferEqual(enc.iv, iv)).to.be.true;
expect(bufferEqual(enc.ephemPublicKey, ephemPublicKey)).to.be.true;
expect(bufferEqual(enc.ciphertext, ciphertext)).to.be.true;
expect(bufferEqual(enc.mac, mac)).to.be.true;
});
it("should decrypt", async () => {
const msg = await eccrypto.decrypt(privateKeyB, decOpts);
expect(msg.toString()).to.equal("test");
});
it("should encrypt and decrypt", async () => {
const enc = await eccrypto.encrypt(publicKeyA, Buffer.from("to a"));
const msg = await eccrypto.decrypt(privateKeyA, enc);
expect(msg.toString()).to.equal("to a");
});
it("should encrypt and decrypt with message size > 15", async () => {
const enc = await eccrypto.encrypt(
publicKeyA,
Buffer.from("message size that is greater than 15 for sure =)")
);
const msg = await eccrypto.decrypt(privateKeyA, enc);
expect(msg.toString()).to.equal(
"message size that is greater than 15 for sure =)"
);
});
it("should encrypt with compressed public key", async () => {
const enc = await eccrypto.encrypt(
publicKeyBCompressed,
Buffer.from("test"),
encOpts
);
expect(bufferEqual(enc.iv, iv)).to.be.true;
expect(bufferEqual(enc.ephemPublicKey, ephemPublicKey)).to.be.true;
expect(bufferEqual(enc.ciphertext, ciphertext)).to.be.true;
expect(bufferEqual(enc.mac, mac)).to.be.true;
});
it("should encrypt and decrypt with compressed public key", async () => {
const enc = await eccrypto.encrypt(
publicKeyACompressed,
Buffer.from("to a")
);
const msg = await eccrypto.decrypt(privateKeyA, enc);
expect(msg.toString()).to.equal("to a");
});
it("should encrypt and decrypt with generated private and public key", async () => {
const privateKey = eccrypto.generatePrivate();
const publicKey = eccrypto.getPublic(privateKey);
const enc = await eccrypto.encrypt(
publicKey,
Buffer.from("generated private key")
);
const msg = await eccrypto.decrypt(privateKey, enc);
expect(msg.toString()).to.equal("generated private key");
});
it("should reject promise on bad private key when decrypting", async () => {
const enc = await eccrypto.encrypt(publicKeyA, Buffer.from("test"));
await expect(eccrypto.decrypt(privateKeyB, enc)).to.eventually.be.rejected;
});
it("should reject promise on bad IV when decrypting", async () => {
const enc = await eccrypto.encrypt(publicKeyA, Buffer.from("test"));
enc.iv[0] ^= 1;
await expect(eccrypto.decrypt(privateKeyA, enc)).to.eventually.be.rejected;
});
it("should reject promise on bad R when decrypting", async () => {
const enc = await eccrypto.encrypt(publicKeyA, Buffer.from("test"));
enc.ephemPublicKey[0] ^= 1;
await expect(eccrypto.decrypt(privateKeyA, enc)).to.eventually.be.rejected;
});
it("should reject promise on bad ciphertext when decrypting", async () => {
const enc = await eccrypto.encrypt(publicKeyA, Buffer.from("test"));
enc.ciphertext[0] ^= 1;
await expect(eccrypto.decrypt(privateKeyA, enc)).to.eventually.be.rejected;
});
it("should reject promise on bad MAC when decrypting", async () => {
const enc = await eccrypto.encrypt(publicKeyA, Buffer.from("test"));
const origMac = enc.mac;
enc.mac = mac.subarray(1);
await expect(eccrypto.decrypt(privateKeyA, enc)).to.eventually.be.rejected;
enc.mac = origMac;
enc.mac[10] ^= 1;
await expect(eccrypto.decrypt(privateKeyA, enc)).to.eventually.be.rejected;
});
});
describe("ECIES Sync", () => {
const ephemPrivateKey = Buffer.alloc(32);
ephemPrivateKey.fill(4);
const ephemPublicKey = eccrypto.getPublic(ephemPrivateKey);
const iv = Buffer.alloc(16);
iv.fill(5);
const ciphertext = Buffer.from("bbf3f0e7486b552b0e2ba9c4ca8c4579", "hex");
const mac = Buffer.from(
"dbb14a9b53dbd6b763dba24dc99520f570cdf8095a8571db4bf501b535fda1ed",
"hex"
);
const encOpts = { ephemPrivateKey: ephemPrivateKey, iv: iv };
const decOpts = {
iv: iv,
ephemPublicKey: ephemPublicKey,
ciphertext: ciphertext,
mac: mac,
};
it("should encrypt", () => {
const enc = eccrypto.encryptSync(publicKeyB, Buffer.from("test"), encOpts);
expect(bufferEqual(enc.iv, iv)).to.be.true;
expect(bufferEqual(enc.ephemPublicKey, ephemPublicKey)).to.be.true;
expect(bufferEqual(enc.ciphertext, ciphertext)).to.be.true;
expect(bufferEqual(enc.mac, mac)).to.be.true;
});
it("should decrypt", () => {
const msg = eccrypto.decryptSync(privateKeyB, decOpts);
expect(msg.toString()).to.equal("test");
});
it("should encrypt and decrypt", () => {
const enc = eccrypto.encryptSync(publicKeyA, Buffer.from("to a"));
const msg = eccrypto.decryptSync(privateKeyA, enc);
expect(msg.toString()).to.equal("to a");
});
it("should encrypt and decrypt with message size > 15", () => {
const enc = eccrypto.encryptSync(
publicKeyA,
Buffer.from("message size that is greater than 15 for sure =)")
);
const msg = eccrypto.decryptSync(privateKeyA, enc);
expect(msg.toString()).to.equal(
"message size that is greater than 15 for sure =)"
);
});
it("should encrypt with compressed public key", () => {
const enc = eccrypto.encryptSync(
publicKeyBCompressed,
Buffer.from("test"),
encOpts
);
expect(bufferEqual(enc.iv, iv)).to.be.true;
expect(bufferEqual(enc.ephemPublicKey, ephemPublicKey)).to.be.true;
expect(bufferEqual(enc.ciphertext, ciphertext)).to.be.true;
expect(bufferEqual(enc.mac, mac)).to.be.true;
});
it("should encrypt and decrypt with compressed public key", () => {
const enc = eccrypto.encryptSync(publicKeyACompressed, Buffer.from("to a"));
const msg = eccrypto.decryptSync(privateKeyA, enc);
expect(msg.toString()).to.equal("to a");
});
it("should encrypt and decrypt with generated private and public key", () => {
const privateKey = eccrypto.generatePrivate();
const publicKey = eccrypto.getPublic(privateKey);
const enc = eccrypto.encryptSync(
publicKey,
Buffer.from("generated private key")
);
const msg = eccrypto.decryptSync(privateKey, enc);
expect(msg.toString()).to.equal("generated private key");
});
it("should throw on bad private key when decrypting", () => {
const enc = eccrypto.encryptSync(publicKeyA, Buffer.from("test"));
expect(() => eccrypto.decryptSync(privateKeyB, enc)).to.throw;
});
it("should throw on bad IV when decrypting", () => {
const enc = eccrypto.encryptSync(publicKeyA, Buffer.from("test"));
enc.iv[0] ^= 1;
expect(() => eccrypto.decryptSync(privateKeyA, enc)).to.throw;
});
it("should throw on bad R when decrypting", () => {
const enc = eccrypto.encryptSync(publicKeyA, Buffer.from("test"));
enc.ephemPublicKey[0] ^= 1;
expect(() => eccrypto.decryptSync(privateKeyA, enc)).to.throw;
});
it("should throw on bad ciphertext when decrypting", () => {
const enc = eccrypto.encryptSync(publicKeyA, Buffer.from("test"));
enc.ciphertext[0] ^= 1;
expect(() => eccrypto.decryptSync(privateKeyA, enc)).to.throw;
});
it("should throw on bad MAC when decrypting", () => {
const enc = eccrypto.encryptSync(publicKeyA, Buffer.from("test"));
const origMac = enc.mac;
enc.mac = mac.subarray(1);
expect(() => eccrypto.decryptSync(privateKeyA, enc)).to.throw;
enc.mac = origMac;
enc.mac[10] ^= 1;
expect(() => eccrypto.decryptSync(privateKeyA, enc)).to.throw;
});
});