Skip to content

Commit

Permalink
perf: Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
Pavel Zarecky committed Jan 6, 2022
1 parent 2b8fdb6 commit 03001f9
Show file tree
Hide file tree
Showing 9 changed files with 3,240 additions and 149,672 deletions.
3 changes: 0 additions & 3 deletions .github/FUNDING.yml

This file was deleted.

2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
12.19.1
14.18.2
147,391 changes: 0 additions & 147,391 deletions .yarn/releases/yarn-1.22.4.js

This file was deleted.

5 changes: 0 additions & 5 deletions .yarnrc

This file was deleted.

15 changes: 2 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
# typescript-library-template [![GitHub license](https://img.shields.io/github/license/vvo/typescript-library-template?style=flat)](https://github.com/vvo/typescript-library-template/blob/master/LICENSE) [![Tests](https://github.com/vvo/typescript-library-template/workflows/CI/badge.svg)](https://github.com/vvo/typescript-library-template/actions) [![codecov](https://codecov.io/gh/vvo/typescript-library-template/branch/master/graph/badge.svg)](https://codecov.io/gh/vvo/typescript-library-template) ![npm](https://img.shields.io/npm/v/typescript-library-template) [![minizipped size](https://badgen.net/bundlephobia/minzip/typescript-library-template)](https://bundlephobia.com/result?p=typescript-library-template)
# X448-js [![GitHub license](https://img.shields.io/github/license/Iskander508/X448-js?style=flat)](https://github.com/Iskander508/X448-js/blob/master/LICENSE) [![Tests](https://github.com/Iskander508/X448-js/workflows/CI/badge.svg)](https://github.com/Iskander508/X448-js/actions) [![codecov](https://codecov.io/gh/vvo/typescript-library-template/branch/master/graph/badge.svg)](https://codecov.io/gh/vvo/typescript-library-template) ![npm](https://img.shields.io/npm/v/x448-js)

<p align="center">
<small><b>Click below to create a new GitHub repository using this template:</b></small>
<br/><br/><a href="https://github.com/vvo/typescript-library-template/generate">
<img src="https://img.shields.io/badge/use%20this-template-blue?logo=github">
</a>
</p>

---

**This TypeScript library template** allows you to easily develop, collaborate on and publish a TypeScript library with all the modern tooling you'd expect from the current TypeScript ecosystem.

This is basically [javascript-library-template](https://github.com/vvo/javascript-library-template) but using [tsdx](https://tsdx.io/) for everything.
Pure JavaScript/TypeScript implementation of the X448 elliptic curve (RFC 7748) for ECDH key exchange. Uses the `jsbn` library for big integer operations.
30 changes: 17 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
{
"name": "typescript-library-template",
"name": "x448-js",
"version": "0.0.0-development",
"private": false,
"description": "TypeScript library template to focus on ⌨️ coding, 🙌 collaborating and 🚀 shipping",
"description": "Pure JS/TS library with X448 curve for ECDH",
"keywords": [
"X448",
"ECDH"
],
"repository": {
"type": "git",
"url": "https://github.com/vvo/typescript-library-template.git"
"url": "https://github.com/Iskander508/X448-js.git"
},
"license": "MIT",
"author": "Vincent Voyer <vincent@codeagain.com>",
"author": "Pavel Zarecky <zarecky@procivis.ch>",
"sideEffects": false,
"main": "dist/index.js",
"module": "dist/typescript-library-template.esm.js",
"module": "dist/x448-js.esm.js",
"typings": "dist/index.d.ts",
"files": [
"src/",
Expand All @@ -31,21 +35,25 @@
"prettier": {
"trailingComma": "all"
},
"dependencies": {
"jsbn": "^1.1.0"
},
"devDependencies": {
"@types/jsbn": "^1.2.30",
"@typescript-eslint/eslint-plugin": "4.33.0",
"@typescript-eslint/parser": "4.33.0",
"eslint-plugin-import": "2.25.2",
"eslint-plugin-jest": "25.0.1",
"prettier-plugin-packagejson": "2.2.13",
"semantic-release": "17.2.4",
"semantic-release": "^18.0.1",
"semantic-release-cli": "5.4.4",
"tsdx": "0.14.1",
"tslib": "2.0.3",
"typescript": "4.0.8"
"tslib": "2.3.1",
"typescript": "4.4.4"
},
"peerDependencies": {},
"engines": {
"node": ">=12"
"node": ">=14.17"
},
"eslint": {
"env": {
Expand All @@ -66,10 +74,6 @@
"plugin:@typescript-eslint/recommended"
],
"rules": {
"arrow-body-style": [
"error",
"always"
],
"curly": "error",
"import/order": [
"error",
Expand Down
70 changes: 66 additions & 4 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,69 @@
import { sum } from "./";
import { getPublicKey, getSharedSecret } from "./";

describe("blah", () => {
it("works", () => {
expect(sum(1, 1)).toEqual(2);
describe("X448", () => {
it("matches shared secret for 2 generated key pairs", () => {
function random(length: number): number[] {
return Array.from({ length }, () => Math.floor(Math.random() * 256));
}

const a_priv = random(56);
const b_priv = random(56);

const a_pub = getPublicKey(a_priv);
const b_pub = getPublicKey(b_priv);

const k_ab = getSharedSecret(a_priv, b_pub);
const k_ba = getSharedSecret(b_priv, a_pub);

expect(k_ab).toEqual(k_ba);
});

// https://datatracker.ietf.org/doc/html/rfc7748#section-6.2
it("matches example from RFC", () => {
const a_priv = Array.from(
Buffer.from(
"9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b",
"hex",
),
);
const b_priv = Array.from(
Buffer.from(
"1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d",
"hex",
),
);

const a_pub = getPublicKey(a_priv);
const b_pub = getPublicKey(b_priv);

expect(a_pub).toEqual(
Array.from(
Buffer.from(
"9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0",
"hex",
),
),
);

expect(b_pub).toEqual(
Array.from(
Buffer.from(
"3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609",
"hex",
),
),
);

const k_ab = getSharedSecret(a_priv, b_pub);
const k_ba = getSharedSecret(b_priv, a_pub);
expect(k_ab).toEqual(k_ba);
expect(k_ab).toEqual(
Array.from(
Buffer.from(
"07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d",
"hex",
),
),
);
});
});
175 changes: 170 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,171 @@
export const sum = (a: number, b: number): number => {
if ("development" === process.env.NODE_ENV) {
console.log("boop");
import { BigInteger } from "jsbn";

function fromNumber(x: number): BigInteger {
return new BigInteger(x.toString(10));
}

function toNumber(num: BigInteger): number {
return num.intValue();
}

const N0 = BigInteger.ZERO;
const N1 = BigInteger.ONE;
const N2 = fromNumber(2);
const N3 = fromNumber(3);
const N5 = fromNumber(5);
const N128 = fromNumber(128);
const N255 = fromNumber(255);

function range(length: number) {
return Array.from({ length }, (_, i) => i);
}

function sqr(num: BigInteger) {
return num.multiply(num);
}

// # Defined here https://tools.ietf.org/html/rfc7748#section-5
// P = 2 ** 448 - 2 ** 224 - 1
const P = new BigInteger(
"726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439",
);
const A24 = new BigInteger("39081");

function cswap(
swap: BigInteger,
x_2: BigInteger,
x_3: BigInteger,
): [BigInteger, BigInteger] {
const dummy = swap.multiply(x_2.subtract(x_3)).mod(P);
x_2 = x_2.subtract(dummy).mod(P);
x_3 = x_3.add(dummy).mod(P);
return [x_2, x_3];
}

function X448(k: BigInteger, u: BigInteger): BigInteger {
const x_1 = u;
let x_2 = N1;
let z_2 = N0;
let x_3 = u;
let z_3 = N1;
let swap = N0;

for (const t of range(448).reverse()) {
const k_t = k.shiftRight(t).and(N1);
swap = swap.xor(k_t);
{
const [a, b] = cswap(swap, x_2, x_3);
x_2 = a;
x_3 = b;
}
{
const [a, b] = cswap(swap, z_2, z_3);
z_2 = a;
z_3 = b;
}
swap = k_t;

const A = x_2.add(z_2).mod(P);
const AA = sqr(A).mod(P);
const B = x_2.subtract(z_2).mod(P);
const BB = sqr(B).mod(P);
const E = AA.subtract(BB).mod(P);
const C = x_3.add(z_3).mod(P);
const D = x_3.subtract(z_3).mod(P);
const DA = D.multiply(A).mod(P);
const CB = C.multiply(B).mod(P);
x_3 = sqr(DA.add(CB).mod(P)).mod(P);
z_3 = x_1.multiply(sqr(DA.subtract(CB).mod(P))).mod(P);
x_2 = AA.multiply(BB).mod(P);
z_2 = E.multiply(AA.add(A24.multiply(E).mod(P)).mod(P)).mod(P);
}
return a + b;
};

const [X_2] = cswap(swap, x_2, x_3);
const [Z_2] = cswap(swap, z_2, z_3);
return X_2.multiply(Z_2.modPow(P.subtract(N2), P)).mod(P);
}

function decodeLittleEndian(b: number[]): BigInteger {
return b
.map((value, i) => fromNumber(value).shiftLeft(8 * i))
.reduce<BigInteger>((aggr, value) => aggr.add(value), N0);
}

function decodeScalar448(k: number[]): BigInteger {
const k_list = [...k];
k_list[0] &= 252;
k_list[55] |= 128;
return decodeLittleEndian(k_list);
}

function unpack(s: number[]): BigInteger {
if (s.length !== 56) {
throw new Error(`Invalid Curve448 scalar (len=${s.length})`);
}
return s
.map((value, i) => fromNumber(value).shiftLeft(8 * i))
.reduce<BigInteger>((aggr, value) => aggr.add(value), N0);
}

function pack(n: BigInteger): number[] {
return Array.from({ length: 56 }, (_, i) =>
toNumber(n.shiftRight(8 * i).and(N255)),
);
}

function clamp(n: BigInteger): BigInteger {
return n.andNot(N3).or(N128.shiftLeft(8 * 55));
}

function multscalar(n: number[], p: number[]): number[] {
const _n = clamp(decodeScalar448(n));
const _p = unpack(p);
return pack(X448(_n, _p));
}

function base_point_mult(n: number[]): number[] {
const _n = clamp(decodeScalar448(n));
return pack(X448(_n, N5));
}

/**
* Calculate X448 public key from private key
* @param {ArrayLike} privateKey Can be any random generated byte-array of length 56
* @returns {Array} Public key derived from the {@link privateKey}
*/
export function getPublicKey(
privateKey: number[] | Uint8Array | Buffer,
): number[] {
if (!privateKey) {
throw new Error("Missing private key");
}

if (privateKey.length !== 56) {
throw new Error(`Invalid Curve448 private key (len=${privateKey.length})`);
}

return base_point_mult(Array.from(privateKey));
}

/**
* Calculate X448 shared secret
* @param {ArrayLike} privateKey Local private key
* @param {ArrayLike} publicKey Remote public key (Can be generated using {@link getPublicKey})
* @returns {Array} Shared secret
*/
export function getSharedSecret(
privateKey: number[] | Uint8Array | Buffer,
publicKey: number[] | Uint8Array | Buffer,
): number[] {
if (!privateKey) {
throw new Error("Missing private key");
}
if (!publicKey) {
throw new Error("Missing public key");
}
if (privateKey.length !== 56) {
throw new Error(`Invalid Curve448 private key (len=${privateKey.length})`);
}

return multscalar(Array.from(privateKey), Array.from(publicKey));
}

0 comments on commit 03001f9

Please sign in to comment.