generated from vvo/typescript-library-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Pavel Zarecky
committed
Jan 6, 2022
1 parent
2b8fdb6
commit 03001f9
Showing
9 changed files
with
3,240 additions
and
149,672 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
12.19.1 | ||
14.18.2 |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
), | ||
), | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} |
Oops, something went wrong.