Skip to content

Commit

Permalink
Bump dependencies & revamp tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kigawas committed Oct 14, 2023
1 parent 73fd142 commit 0ec0f7e
Show file tree
Hide file tree
Showing 13 changed files with 1,695 additions and 993 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,9 @@ readonly compressed: Buffer;

## Release Notes

### 0.3.1 ~ 0.3.17
### 0.3.1 ~ 0.3.18

- Revamp tests
- Support Node 18, 20
- Drop Node 10, 12 support
- Bump dependencies
Expand Down
8 changes: 8 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
preset: "ts-jest",
testEnvironment: "node",
collectCoverage: true,
coverageDirectory: "coverage",
coverageProvider: "v8",
testTimeout: 30000,
};
26 changes: 13 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,27 @@
],
"scripts": {
"build": "npx tsc",
"test": "nyc -r lcov -e .ts mocha -r ts-node/register tests/**/*.test.ts && nyc report --reporter=json"
"test": "jest"
},
"repository": {
"type": "git",
"url": "https://github.com/ecies/js.git"
},
"version": "0.3.17",
"version": "0.3.18",
"dependencies": {
"@types/secp256k1": "^4.0.3",
"futoin-hkdf": "^1.5.1",
"@types/secp256k1": "^4.0.4",
"futoin-hkdf": "^1.5.3",
"secp256k1": "^5.0.0"
},
"devDependencies": {
"@types/chai": "^4.3.3",
"@types/mocha": "^10.0.0",
"@types/node": "^20.2.3",
"axios": "^1.4.0",
"chai": "^4.3.6",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"ts-node": "^10.9.0",
"typescript": "^5.0.4"
"@types/jest": "^29.5.5",
"@types/node": "^20.8.6",
"@types/node-fetch": "^2.6.6",
"https-proxy-agent": "^7.0.2",
"jest": "^29.7.0",
"node-fetch": "^2.7.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
}
}
16 changes: 4 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import { PrivateKey, PublicKey } from "./keys";
import {
aesDecrypt,
aesEncrypt,
decodeHex,
getValidSecret,
remove0x,
} from "./utils";
import { UNCOMPRESSED_PUBLIC_KEY_SIZE } from "./consts";
import { PrivateKey, PublicKey } from "./keys";
import { aesDecrypt, aesEncrypt, decodeHex, getValidSecret, remove0x } from "./utils";

export function encrypt(receiverRawPK: string | Buffer, msg: Buffer): Buffer {
const ephemeralKey = new PrivateKey();
Expand All @@ -27,10 +21,8 @@ export function decrypt(receiverRawSK: string | Buffer, msg: Buffer): Buffer {
? new PrivateKey(receiverRawSK)
: PrivateKey.fromHex(receiverRawSK);

const senderPubkey = new PublicKey(
msg.slice(0, UNCOMPRESSED_PUBLIC_KEY_SIZE)
);
const encrypted = msg.slice(UNCOMPRESSED_PUBLIC_KEY_SIZE);
const senderPubkey = new PublicKey(msg.subarray(0, UNCOMPRESSED_PUBLIC_KEY_SIZE));
const encrypted = msg.subarray(UNCOMPRESSED_PUBLIC_KEY_SIZE);
const aesKey = senderPubkey.decapsulate(receiverSK);
return aesDecrypt(aesKey, encrypted);
}
Expand Down
17 changes: 5 additions & 12 deletions src/keys/PrivateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import hkdf from "futoin-hkdf";
import secp256k1 from "secp256k1";

import { decodeHex, getValidSecret } from "../utils";
import PublicKey from "./PublicKey";
import { PublicKey } from "./PublicKey";

export default class PrivateKey {
export class PrivateKey {
public static fromHex(hex: string): PrivateKey {
return new PrivateKey(decodeHex(hex));
}
Expand All @@ -17,29 +17,22 @@ export default class PrivateKey {
if (!secp256k1.privateKeyVerify(this.secret)) {
throw new Error("Invalid private key");
}
this.publicKey = new PublicKey(
Buffer.from(secp256k1.publicKeyCreate(this.secret))
);
this.publicKey = new PublicKey(Buffer.from(secp256k1.publicKeyCreate(this.secret)));
}

public toHex(): string {
return `0x${this.secret.toString("hex")}`;
}

public encapsulate(pub: PublicKey): Buffer {
const master = Buffer.concat([
this.publicKey.uncompressed,
this.multiply(pub),
]);
const master = Buffer.concat([this.publicKey.uncompressed, this.multiply(pub)]);
return hkdf(master, 32, {
hash: "SHA-256",
});
}

public multiply(pub: PublicKey): Buffer {
return Buffer.from(
secp256k1.publicKeyTweakMul(pub.compressed, this.secret, false)
);
return Buffer.from(secp256k1.publicKeyTweakMul(pub.compressed, this.secret, false));
}

public equals(other: PrivateKey): boolean {
Expand Down
6 changes: 3 additions & 3 deletions src/keys/PublicKey.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import hkdf from "futoin-hkdf";
import secp256k1 from "secp256k1";

import { decodeHex } from "../utils";
import { UNCOMPRESSED_PUBLIC_KEY_SIZE } from "../consts";
import PrivateKey from "./PrivateKey";
import { decodeHex } from "../utils";
import { PrivateKey } from "./PrivateKey";

export default class PublicKey {
export class PublicKey {
public static fromHex(hex: string): PublicKey {
const decoded = decodeHex(hex);
if (decoded.length === UNCOMPRESSED_PUBLIC_KEY_SIZE - 1) {
Expand Down
4 changes: 2 additions & 2 deletions src/keys/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default as PrivateKey } from "./PrivateKey";
export { default as PublicKey } from "./PublicKey";
export { PrivateKey } from "./PrivateKey";
export { PublicKey } from "./PublicKey";
12 changes: 4 additions & 8 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
import secp256k1 from "secp256k1";

import {
AES_IV_LENGTH,
AES_IV_PLUS_TAG_LENGTH,
SECRET_KEY_LENGTH,
} from "./consts";
import { AES_IV_LENGTH, AES_IV_PLUS_TAG_LENGTH, SECRET_KEY_LENGTH } from "./consts";

export function remove0x(hex: string): string {
if (hex.startsWith("0x") || hex.startsWith("0X")) {
Expand Down Expand Up @@ -35,9 +31,9 @@ export function aesEncrypt(key: Buffer, plainText: Buffer): Buffer {
}

export function aesDecrypt(key: Buffer, cipherText: Buffer): Buffer {
const nonce = cipherText.slice(0, AES_IV_LENGTH);
const tag = cipherText.slice(AES_IV_LENGTH, AES_IV_PLUS_TAG_LENGTH);
const ciphered = cipherText.slice(AES_IV_PLUS_TAG_LENGTH);
const nonce = cipherText.subarray(0, AES_IV_LENGTH);
const tag = cipherText.subarray(AES_IV_LENGTH, AES_IV_PLUS_TAG_LENGTH);
const ciphered = cipherText.subarray(AES_IV_PLUS_TAG_LENGTH);
const decipher = createDecipheriv("aes-256-gcm", key, nonce);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(ciphered), decipher.final()]);
Expand Down
50 changes: 7 additions & 43 deletions tests/crypt.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import axios from "axios";
import { expect } from "chai";
import { randomBytes } from "crypto";
import { stringify } from "querystring";

import { decrypt, encrypt } from "../src/index";
import { PrivateKey, PublicKey } from "../src/keys";
import { aesDecrypt, aesEncrypt, decodeHex } from "../src/utils";

const PYTHON_BACKEND = "https://ecies.deta.dev/";

describe("test encrypt and decrypt", () => {
const TEXT = "helloworld";

it("tests aes with random key", () => {
const key = randomBytes(32);
const data = Buffer.from("this is a test");
expect(data.equals(aesDecrypt(key, aesEncrypt(key, data)))).to.be.equal(true);
expect(data.equals(aesDecrypt(key, aesEncrypt(key, data)))).toEqual(true);
});

it("tests aes decrypt with known key and TEXT", () => {
Expand All @@ -28,27 +23,27 @@ describe("test encrypt and decrypt", () => {

const data = Buffer.concat([nonce, tag, encrypted]);
const decrypted = aesDecrypt(key, data);
expect(decrypted.toString()).to.be.equal(TEXT);
expect(decrypted.toString()).toEqual(TEXT);
});

it("tests encrypt/decrypt buffer", () => {
const prv1 = new PrivateKey();
const encrypted1 = encrypt(prv1.publicKey.uncompressed, Buffer.from(TEXT));
expect(decrypt(prv1.secret, encrypted1).toString()).to.be.equal(TEXT);
expect(decrypt(prv1.secret, encrypted1).toString()).toEqual(TEXT);

const prv2 = new PrivateKey();
const encrypted2 = encrypt(prv2.publicKey.compressed, Buffer.from(TEXT));
expect(decrypt(prv2.secret, encrypted2).toString()).to.be.equal(TEXT);
expect(decrypt(prv2.secret, encrypted2).toString()).toEqual(TEXT);
});

it("tests encrypt/decrypt hex", () => {
const prv1 = new PrivateKey();
const encrypted1 = encrypt(prv1.publicKey.toHex(), Buffer.from(TEXT));
expect(decrypt(prv1.toHex(), encrypted1).toString()).to.be.equal(TEXT);
expect(decrypt(prv1.toHex(), encrypted1).toString()).toEqual(TEXT);

const prv2 = new PrivateKey();
const encrypted2 = encrypt(prv2.publicKey.toHex(), Buffer.from(TEXT));
expect(decrypt(prv2.toHex(), encrypted2).toString()).to.be.equal(TEXT);
expect(decrypt(prv2.toHex(), encrypted2).toString()).toEqual(TEXT);
});

it("tests sk pk", () => {
Expand All @@ -59,37 +54,6 @@ describe("test encrypt and decrypt", () => {
"048e41409f2e109f2d704f0afd15d1ab53935fd443729913a7e8536b4cef8cf5773d4db7bbd99e9ed64595e24a251c9836f35d4c9842132443c17f6d501b3410d2"
);
const enc = encrypt(pk.toHex(), Buffer.from(TEXT));
expect(decrypt(sk.toHex(), enc).toString()).to.be.equal(TEXT);
});

it("tests encrypt/decrypt against python version", () => {
const prv = new PrivateKey();

axios
.post(
PYTHON_BACKEND,
stringify({
data: TEXT,
pub: prv.publicKey.toHex(),
})
)
.then((res) => {
const encryptedKnown = Buffer.from(decodeHex(res.data));
const decrypted = decrypt(prv.toHex(), encryptedKnown);
expect(decrypted.toString()).to.be.equal(TEXT);
});

const encrypted = encrypt(prv.publicKey.toHex(), Buffer.from(TEXT));
axios
.post(
PYTHON_BACKEND,
stringify({
data: encrypted.toString("hex"),
prv: prv.toHex(),
})
)
.then((res) => {
expect(TEXT).to.be.equal(res.data);
});
expect(decrypt(sk.toHex(), enc).toString()).toEqual(TEXT);
});
});
51 changes: 51 additions & 0 deletions tests/integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { HttpsProxyAgent } from "https-proxy-agent";
import fetch, { RequestInit } from "node-fetch";

import { PrivateKey, decrypt, encrypt, utils } from "../src/index";

const decodeHex = utils.decodeHex;

const PYTHON_BACKEND = "https://eciespydemo-1-d5397785.deta.app/";
const TEXT = "helloworld🌍";

describe("test encrypt and decrypt against python version", () => {
it("tests encrypt", async () => {
const sk = new PrivateKey();
const res = await eciesApi(PYTHON_BACKEND, {
data: TEXT,
pub: sk.publicKey.toHex(),
});
const decrypted = decrypt(sk.toHex(), decodeHex(await res.text()));
expect(decrypted.toString()).toEqual(TEXT);
});

it("tests decrypt", async () => {
const sk = new PrivateKey();
const encrypted = encrypt(sk.publicKey.toHex(), Buffer.from(TEXT));
const res = await eciesApi(PYTHON_BACKEND, {
data: encrypted.toString("hex"),
prv: sk.toHex(),
});
expect(TEXT).toEqual(await res.text());
});
});

async function eciesApi(
url: string,
body: { data: string; pub?: string; prv?: string }
) {
const config: RequestInit = {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
};
if (process.env.http_proxy !== undefined) {
config.agent = new HttpsProxyAgent(`${process.env.http_proxy}`);
}

return await fetch(url, {
...config,
body: new URLSearchParams(body),
});
}
29 changes: 11 additions & 18 deletions tests/keys.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { expect } from "chai";

import { PrivateKey, PublicKey } from "../src/keys";
import { decodeHex } from "../src/utils";

const ETH_PRVHEX =
"0x95d3c5e483e9b1d4f5fc8e79b2deaf51362980de62dbb082a9a4257eef653d7d";
const ETH_PRVHEX = "0x95d3c5e483e9b1d4f5fc8e79b2deaf51362980de62dbb082a9a4257eef653d7d";
const ETH_PUBHEX =
"0x98afe4f150642cd05cc9d2fa36458ce0a58567daeaf5fde7333ba9b403011140" +
"a4e28911fcf83ab1f457a30b4959efc4b9306f514a4c3711a16a80e3b47eb58b";
Expand All @@ -14,30 +11,30 @@ describe("test keys", () => {
// 0 < private key < group order int
const groupOrderInt =
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141";
expect(() => new PrivateKey(decodeHex(groupOrderInt))).to.throw(Error);
expect(() => new PrivateKey(decodeHex(groupOrderInt))).toThrow(Error);

const groupOrderIntAdd1 =
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142";
expect(() => new PrivateKey(decodeHex(groupOrderIntAdd1))).to.throw(Error);
expect(() => new PrivateKey(decodeHex(groupOrderIntAdd1))).toThrow(Error);

expect(() => new PrivateKey(decodeHex("0"))).to.throw(Error);
expect(() => new PrivateKey(decodeHex("0"))).toThrow(Error);
});

it("tests equal", () => {
const prv = new PrivateKey();
const pub = PublicKey.fromHex(prv.publicKey.toHex(false));

const isPubEqual = pub.uncompressed.equals(prv.publicKey.uncompressed);
expect(isPubEqual).to.be.equal(true);
expect(isPubEqual).toEqual(true);

const isFromHexWorking = prv.equals(PrivateKey.fromHex(prv.toHex()));
expect(isFromHexWorking).to.be.equal(true);
expect(isFromHexWorking).toEqual(true);
});

it("tests eth key compatibility", () => {
const ethPrv = PrivateKey.fromHex(ETH_PRVHEX);
const ethPub = PublicKey.fromHex(ETH_PUBHEX);
expect(ethPub.equals(ethPrv.publicKey)).to.be.equal(true);
expect(ethPub.equals(ethPrv.publicKey)).toEqual(true);
});

it("tests multiply and hkdf", () => {
Expand All @@ -48,18 +45,14 @@ describe("test keys", () => {

const k1 = new PrivateKey(two);
const k2 = new PrivateKey(three);
expect(
k1.multiply(k2.publicKey).equals(k2.multiply(k1.publicKey))
).to.be.equal(true);
expect(k1.multiply(k2.publicKey).equals(k2.multiply(k1.publicKey))).toEqual(true);

const derived = k1.encapsulate(k2.publicKey);
const anotherDerived = k1.publicKey.decapsulate(k2);
const knownDerived = Buffer.from(
decodeHex(
"6f982d63e8590c9d9b5b4c1959ff80315d772edd8f60287c9361d548d5200f82"
)
decodeHex("6f982d63e8590c9d9b5b4c1959ff80315d772edd8f60287c9361d548d5200f82")
);
expect(derived.equals(knownDerived)).to.be.equal(true);
expect(anotherDerived.equals(knownDerived)).to.be.equal(true);
expect(derived.equals(knownDerived)).toEqual(true);
expect(anotherDerived.equals(knownDerived)).toEqual(true);
});
});
Loading

0 comments on commit 0ec0f7e

Please sign in to comment.