Skip to content

Commit

Permalink
Change secp256k1 and test library
Browse files Browse the repository at this point in the history
  • Loading branch information
kigawas committed Jun 28, 2023
1 parent 8902d54 commit 5dda903
Show file tree
Hide file tree
Showing 13 changed files with 1,558 additions and 964 deletions.
5 changes: 4 additions & 1 deletion .cspell.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"Codacy",
"Codecov",
"consts",
"ecies",
"eciespy",
"eciesjs",
"eth",
Expand All @@ -26,7 +27,9 @@
// flagWords - list of words to be always considered incorrect
// This is useful for offensive words and common spelling errors.
// For example "hte" should be "the"
"flagWords": ["hte"],
"flagWords": [
"hte"
],
"ignorePaths": [
".git",
".github",
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ readonly compressed: Buffer;

## Release Notes

### 0.4.0

- Change secp256k1 library to [noble-curves](https://github.com/paulmillr/noble-curves), which is [audited](https://github.com/paulmillr/noble-curves/tree/main/audit)
- Change test library to [jest](https://jestjs.io/)
- Bump dependencies

### 0.3.1 ~ 0.3.17

- Support Node 18, 20
Expand Down
9 changes: 9 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default {
preset: "ts-jest",
testEnvironment: "node",
clearMocks: true,
collectCoverage: true,
coverageDirectory: "coverage",
coverageProvider: "v8",
testTimeout: 30000,
};
23 changes: 10 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,24 @@
],
"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.4.0",
"dependencies": {
"@types/secp256k1": "^4.0.3",
"futoin-hkdf": "^1.5.1",
"secp256k1": "^5.0.0"
"@noble/curves": "^1.1.0",
"futoin-hkdf": "^1.5.2"
},
"devDependencies": {
"@types/chai": "^4.3.3",
"@types/mocha": "^10.0.0",
"@types/node": "^20.2.3",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.2",
"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"
"jest": "^29.5.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.1.5"
}
}
1 change: 1 addition & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export const AES_IV_LENGTH = 16;
export const AES_TAG_LENGTH = 16;
export const AES_IV_PLUS_TAG_LENGTH = AES_IV_LENGTH + AES_TAG_LENGTH;
export const SECRET_KEY_LENGTH = 32;
export const ONE = BigInt(1);
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
19 changes: 6 additions & 13 deletions src/keys/PrivateKey.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { secp256k1 } from "@noble/curves/secp256k1";
import hkdf from "futoin-hkdf";
import secp256k1 from "secp256k1";

import { decodeHex, getValidSecret } from "../utils";
import PublicKey from "./PublicKey";
Expand All @@ -14,32 +14,25 @@ export default class PrivateKey {

constructor(secret?: Buffer) {
this.secret = secret || getValidSecret();
if (!secp256k1.privateKeyVerify(this.secret)) {
if (!secp256k1.utils.isValidPrivateKey(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.getPublicKey(this.secret)));
}

public toHex(): string {
return `0x${this.secret.toString("hex")}`;
return 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.getSharedSecret(this.secret, pub.compressed, false));
}

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

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

export default class PublicKey {
Expand All @@ -21,8 +21,8 @@ export default class PublicKey {
public readonly compressed: Buffer;

constructor(buffer: Buffer) {
this.uncompressed = Buffer.from(secp256k1.publicKeyConvert(buffer, false));
this.compressed = Buffer.from(secp256k1.publicKeyConvert(buffer, true));
this.uncompressed = Buffer.from(secp256k1.getSharedSecret(ONE, buffer, false));
this.compressed = Buffer.from(secp256k1.getSharedSecret(ONE, buffer, true));
}

public toHex(compressed: boolean = true): string {
Expand Down
10 changes: 3 additions & 7 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { secp256k1 } from "@noble/curves/secp256k1";
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 All @@ -22,7 +18,7 @@ export function getValidSecret(): Buffer {
let key: Buffer;
do {
key = randomBytes(SECRET_KEY_LENGTH);
} while (!secp256k1.privateKeyVerify(key));
} while (!secp256k1.utils.isValidPrivateKey(key));
return key;
}

Expand Down
61 changes: 27 additions & 34 deletions tests/crypt.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
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/";
const PYTHON_BACKEND = "https://eciespydemo-1-d5397785.deta.app/";

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)))).toBe(true);
});

it("tests aes decrypt with known key and TEXT", () => {
Expand All @@ -28,27 +27,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()).toBe(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()).toBe(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()).toBe(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()).toBe(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()).toBe(TEXT);
});

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

it("tests encrypt/decrypt against python version", () => {
it("tests encrypt/decrypt against python version", async () => {
const prv = new PrivateKey();
let res = await axios.post(
PYTHON_BACKEND,
stringify({
data: TEXT,
pub: prv.publicKey.toHex(),
})
);
const encryptedKnown = Buffer.from(decodeHex(res.data));
const decrypted = decrypt(prv.toHex(), encryptedKnown);

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);
});
expect(decrypted.toString()).toBe(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);
});
res = await axios.post(
PYTHON_BACKEND,
stringify({
data: encrypted.toString("hex"),
prv: prv.toHex(),
})
);
expect(TEXT).toBe(res.data);
});
});
41 changes: 20 additions & 21 deletions tests/keys.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { expect } from "chai";

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

const ETH_PRVHEX =
"0x95d3c5e483e9b1d4f5fc8e79b2deaf51362980de62dbb082a9a4257eef653d7d";
const ETH_PUBHEX =
const PRV_HEX = "0x95d3c5e483e9b1d4f5fc8e79b2deaf51362980de62dbb082a9a4257eef653d7d";
const PUB_HEX =
"0x98afe4f150642cd05cc9d2fa36458ce0a58567daeaf5fde7333ba9b403011140" +
"a4e28911fcf83ab1f457a30b4959efc4b9306f514a4c3711a16a80e3b47eb58b";

Expand All @@ -14,30 +11,36 @@ 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).toBe(true);

const isFromHexWorking = prv.equals(PrivateKey.fromHex(prv.toHex()));
expect(isFromHexWorking).to.be.equal(true);
expect(isFromHexWorking).toBe(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);
const ethPrv = PrivateKey.fromHex(PRV_HEX);
const ethPub = PublicKey.fromHex(PUB_HEX);
expect(ethPub.equals(ethPrv.publicKey)).toBe(true);
expect(ethPub.compressed.toString("hex")).toEqual(
"0398afe4f150642cd05cc9d2fa36458ce0a58567daeaf5fde7333ba9b403011140"
);
expect(ethPub.uncompressed.toString("hex")).toEqual(
"0498afe4f150642cd05cc9d2fa36458ce0a58567daeaf5fde7333ba9b403011140a4e28911fcf83ab1f457a30b4959efc4b9306f514a4c3711a16a80e3b47eb58b"
);
});

it("tests multiply and hkdf", () => {
Expand All @@ -48,18 +51,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))).toBe(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)).toBe(true);
expect(anotherDerived.equals(knownDerived)).toBe(true);
});
});
Loading

0 comments on commit 5dda903

Please sign in to comment.