Skip to content

Commit

Permalink
Converted from JavaScript to TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
zigazajc007 committed Aug 15, 2024
1 parent b8c8e32 commit 3d9cf05
Show file tree
Hide file tree
Showing 19 changed files with 787 additions and 1,121 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Publish
on:
push:
branches:
- main

jobs:
publish:
runs-on: ubuntu-latest

permissions:
contents: read
id-token: write

steps:
- uses: actions/checkout@v4

- name: Publish package
run: npx jsr publish
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
node_modules
dist
node_modules
5 changes: 5 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"tabWidth": 2,
"useTabs": true,
"printWidth": 160
}
40 changes: 40 additions & 0 deletions build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import dts from "bun-plugin-dts";
import Logger from "@rabbit-company/logger";
import fs from "fs/promises";

await fs.rm("./module", { recursive: true, force: true });
await fs.rm("./dist", { recursive: true, force: true });

Logger.info("Start bulding module...");
let moduleBuild = await Bun.build({
entrypoints: ["./src/xchacha20.ts"],
outdir: "./module",
target: "browser",
format: "esm",
plugins: [dts({ output: { noBanner: true } })],
});

if (moduleBuild.success) {
Logger.info("Bulding module complete");
} else {
Logger.error("Bulding module failed");
}

fs.cp("./src/index.html", "./dist/index.html", { recursive: true, force: true });

Logger.info("Start bundling dist...");
let distBuild = await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
target: "browser",
format: "esm",
minify: true,
sourcemap: "none",
plugins: [],
});

if (distBuild.success) {
Logger.info("Bundling dist complete");
} else {
Logger.error("Bundling dist failed");
}
Binary file added bun.lockb
Binary file not shown.
27 changes: 27 additions & 0 deletions dist/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>XChaCha20-JS</title>
</head>
<body>
<h1><a href="https://github.com/Rabbit-Company/XChaCha20-JS" target="_blank">XChaCha20-JS</a></h1>
<textarea id="text-1" rows="10" cols="50"></textarea><br /><br />
<div class="buttons">
<input id="secretKey" type="password" placeholder="Secret Key" />
<button class="buttons" id="btn-encrypt">Encrypt</button>
<button class="buttons" id="btn-decrypt">Decrypt</button>
</div>
<br />
<textarea id="text-2" rows="10" cols="50" readonly></textarea><br />

<h1>Performance Test</h1>
<div class="buttons">
<label for="amount">Number of executions:</label>
<input id="amount" type="number" min="1" max="100000" placeholder="Amount" value="1000" />
<button class="buttons" id="btn-start">Start</button>
</div>
<p id="perf"></p>

<script type="module" src="index.js"></script>
</body>
</html>
1 change: 1 addition & 0 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions jsr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "@rabbit-company/xchacha20",
"version": "2.1.0",
"exports": "./src/xchacha20.ts"
}
57 changes: 57 additions & 0 deletions module/xchacha20.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
declare class XChaCha20 {
keystream: number[];
encryptedText: number[];
plaintext: number[];
nonce: number[];
constructor();
rotateleft: (a: number, b: number) => number;
le32: (a: number, b: number, c: number, d: number) => number;
int2(data: string): number[];
Qround(state: Uint32Array, a: number, b: number, c: number, d: number): void;
Inner_Block(state: Uint32Array): void;
Chacha20_BlockFunction(key: number[], nonce: number[], block_counter: number): number[];
HChacha20_BlockFunction(key: number[], nonce: number[]): number[];
chacha20_encrypt(key: number[], counter: number, nonce: number[], plaintext: number[]): void;
chacha20_decrypt(key: number[], counter: number, nonce: number[], eT: number[]): void;
xchacha20_decrypt(key: number[], encryptedText: string): void;
xchacha20_encrypt(key: number[], nonce: number[], plaintext: number[]): void;
private static convertToText;
private static hexEncode;
private static b64EncodeUnicode;
private static b64DecodeUnicode;
private static randomNonce;
/**
* Encrypts a given message using the XChaCha20 encryption algorithm.
*
* @param {string} message - The plain text message that needs to be encrypted.
* @param {string} secretKey - The secret key used for encryption. This should be a secure key.
* @returns {string} The encrypted message, encoded in base64 format.
*
* @example
* const message = "Hello World!";
* const secretKey = "shXiepgJCYF1lTvGzdpRxgrNcvd@6y";
*
* XChaCha20.encrypt(message, secretKey);
*/
static encrypt(message: string, secretKey: string): string;
/**
* Decrypts a given encrypted message using the XChaCha20 decryption algorithm.
*
* @param {string} message - The encrypted message in base64 format that needs to be decrypted.
* @param {string} secretKey - The secret key used for decryption. It should match the key used for encryption.
* @returns {string} The decrypted plain text message.
*
* @example
* const encryptedMessage = "JWTChRvDsMOnTcK4KCU+wpzCkF85wpgGeMKnDio8woHDlsOdJ8OKZSzDiinCv8O4aiDCrFN7K8KQQ8OkwowS";
* const secretKey = "shXiepgJCYF1lTvGzdpRxgrNcvd@6y";
*
* XChaCha20.decrypt(encryptedMessage, secretKey);
*/
static decrypt(message: string, secretKey: string): string;
}

export {
XChaCha20 as default,
};

export {};
223 changes: 223 additions & 0 deletions module/xchacha20.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// src/xchacha20.ts
class XChaCha20 {
keystream;
encryptedText;
plaintext;
nonce;
constructor() {
this.keystream = [];
this.encryptedText = [];
this.plaintext = [];
this.nonce = [];
}
rotateleft = (a, b) => {
return a << b | a >>> 32 - b;
};
le32 = (a, b, c, d) => {
return (a ^ b << 8 ^ c << 16 ^ d << 24) >>> 0;
};
int2(data) {
let result = [];
for (let i = 0;i < data.length; i++) {
const hex = data.charCodeAt(i).toString(16);
result.push(parseInt(hex, 16));
}
return result;
}
Qround(state, a, b, c, d) {
state[a] += state[b];
state[d] ^= state[a];
state[d] = this.rotateleft(state[d], 16);
state[c] += state[d];
state[b] ^= state[c];
state[b] = this.rotateleft(state[b], 12);
state[a] += state[b];
state[d] ^= state[a];
state[d] = this.rotateleft(state[d], 8);
state[c] += state[d];
state[b] ^= state[c];
state[b] = this.rotateleft(state[b], 7);
state[a] >>>= 0;
state[b] >>>= 0;
state[c] >>>= 0;
state[d] >>>= 0;
}
Inner_Block(state) {
this.Qround(state, 0, 4, 8, 12);
this.Qround(state, 1, 5, 9, 13);
this.Qround(state, 2, 6, 10, 14);
this.Qround(state, 3, 7, 11, 15);
this.Qround(state, 0, 5, 10, 15);
this.Qround(state, 1, 6, 11, 12);
this.Qround(state, 2, 7, 8, 13);
this.Qround(state, 3, 4, 9, 14);
}
Chacha20_BlockFunction(key, nonce, block_counter) {
let state = [];
state[0] = 1634760805;
state[1] = 857760878;
state[2] = 2036477234;
state[3] = 1797285236;
state[4] = this.le32(key[0], key[1], key[2], key[3]);
state[5] = this.le32(key[4], key[5], key[6], key[7]);
state[6] = this.le32(key[8], 9, key[10], key[11]);
state[7] = this.le32(key[12], key[13], key[14], key[15]);
state[8] = this.le32(key[16], key[17], 18, key[19]);
state[9] = this.le32(key[20], key[21], 22, key[23]);
state[10] = this.le32(key[24], key[25], key[26], key[27]);
state[11] = this.le32(key[28], key[29], key[30], key[31]);
state[12] = block_counter;
state[13] = this.le32(nonce[0], nonce[1], nonce[2], nonce[3]);
state[14] = this.le32(nonce[4], nonce[5], nonce[6], nonce[7]);
state[15] = this.le32(nonce[8], nonce[9], nonce[10], nonce[11]);
let temp = new Uint32Array(state.slice());
for (let i = 1;i <= 10; i++) {
this.Inner_Block(temp);
}
let Serialized_Block = [];
for (let i = 0, i2 = 0;i < 16; i++) {
state[i] += temp[i];
Serialized_Block[i2++] = state[i] & 255;
Serialized_Block[i2++] = state[i] >>> 8 & 255;
Serialized_Block[i2++] = state[i] >>> 16 & 255;
Serialized_Block[i2++] = state[i] >>> 24 & 255;
}
return Serialized_Block;
}
HChacha20_BlockFunction(key, nonce) {
let state = [];
state[0] = 1634760805;
state[1] = 857760878;
state[2] = 2036477234;
state[3] = 1797285236;
state[4] = this.le32(key[0], key[1], key[2], key[3]);
state[5] = this.le32(key[4], key[5], key[6], key[7]);
state[6] = this.le32(key[8], 9, key[10], key[11]);
state[7] = this.le32(key[12], key[13], key[14], key[15]);
state[8] = this.le32(key[16], key[17], 18, key[19]);
state[9] = this.le32(key[20], key[21], 22, key[23]);
state[10] = this.le32(key[24], key[25], key[26], key[27]);
state[11] = this.le32(key[28], key[29], key[30], key[31]);
state[12] = this.le32(nonce[0], nonce[1], nonce[2], nonce[3]);
state[13] = this.le32(nonce[4], nonce[5], nonce[6], nonce[7]);
state[14] = this.le32(nonce[8], nonce[9], nonce[10], nonce[11]);
state[15] = this.le32(nonce[12], nonce[13], nonce[14], nonce[15]);
let temp = new Uint32Array(state.slice());
for (let i = 1;i <= 10; i++) {
this.Inner_Block(temp);
}
let B1 = temp.slice(0, 4);
let B2 = temp.slice(12, 16);
let B = new Uint32Array(B1.length + B2.length);
B.set(B1);
B.set(B2, B1.length);
let Serialized_Block = [];
for (let i = 0, i2 = 0;i < 8; i++) {
Serialized_Block[i2++] = B[i] & 255;
Serialized_Block[i2++] = B[i] >>> 8 & 255;
Serialized_Block[i2++] = B[i] >>> 16 & 255;
Serialized_Block[i2++] = B[i] >>> 24 & 255;
}
return Serialized_Block;
}
chacha20_encrypt(key, counter, nonce, plaintext) {
let keystream = [];
keystream.push(...this.Chacha20_BlockFunction(key, nonce, counter));
let pos = 0;
for (let i = 0;i < plaintext.length; i++) {
if (pos === 64) {
counter++;
keystream.push(...this.Chacha20_BlockFunction(key, nonce, counter));
pos = 0;
}
plaintext[i] = parseInt(plaintext[i].toString(), 16);
pos++;
}
let cipherText = [];
for (let i = 0;i < plaintext.length; i++) {
cipherText[i] = plaintext[i] ^ keystream[i];
}
this.keystream = keystream;
this.encryptedText = cipherText;
}
chacha20_decrypt(key, counter, nonce, eT) {
let keystream = [];
keystream.push(...this.Chacha20_BlockFunction(key, nonce, counter));
let pos = 0;
for (let i = 0;i < eT.length; i++) {
if (pos === 64) {
counter++;
keystream.push(...this.Chacha20_BlockFunction(key, nonce, counter));
pos = 0;
}
pos++;
}
let pT = [];
for (let i = 0;i < this.encryptedText.length; i++) {
pT[i] = this.encryptedText[i] ^ keystream[i];
}
this.plaintext = pT;
}
xchacha20_decrypt(key, encryptedText) {
this.encryptedText = this.int2(XChaCha20.b64DecodeUnicode(encryptedText));
let nonce = this.encryptedText.slice(-24);
this.encryptedText = this.encryptedText.slice(0, -24);
let subkey = this.HChacha20_BlockFunction(key, nonce.slice(0, 16));
let chacha20_nonce = [0, 0, 0, 0];
chacha20_nonce.push(...nonce.slice(16, 24));
this.chacha20_decrypt(subkey, 0, chacha20_nonce, this.encryptedText);
}
xchacha20_encrypt(key, nonce, plaintext) {
let subkey = this.HChacha20_BlockFunction(key, nonce.slice(0, 16));
let chacha20_nonce = [0, 0, 0, 0];
chacha20_nonce.push(...nonce.slice(16, 24));
this.chacha20_encrypt(subkey, 0, chacha20_nonce, plaintext);
}
static convertToText(data) {
let text = "";
for (let i = 0;i < data.length; i++) {
text += String.fromCharCode(data[i]);
}
return text;
}
static hexEncode(data) {
let result = [];
for (let i = 0;i < data.length; i++) {
result.push(data.charCodeAt(i).toString(16));
}
return result;
}
static b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function toSolidBytes(match, p1) {
return String.fromCharCode(Number("0x" + p1));
}));
}
static b64DecodeUnicode(str) {
return decodeURIComponent(atob(str).split("").map(function(c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
}).join(""));
}
static randomNonce() {
let rand_n = new Uint8Array(24);
crypto.getRandomValues(rand_n);
return rand_n;
}
static encrypt(message, secretKey) {
const hexMessage = XChaCha20.hexEncode(message);
const hexSecretKey = XChaCha20.hexEncode(secretKey);
const nonce = XChaCha20.randomNonce();
const xchacha = new XChaCha20;
xchacha.xchacha20_encrypt(hexSecretKey, Array.from(nonce), hexMessage);
xchacha.encryptedText.push(...Array.from(nonce));
return XChaCha20.b64EncodeUnicode(XChaCha20.convertToText(xchacha.encryptedText));
}
static decrypt(message, secretKey) {
const hexSecretKey = XChaCha20.hexEncode(secretKey);
let d1 = new XChaCha20;
d1.xchacha20_decrypt(hexSecretKey, message);
return XChaCha20.convertToText(d1.plaintext);
}
}
export {
XChaCha20 as default
};
Loading

0 comments on commit 3d9cf05

Please sign in to comment.