diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..ad5781d --- /dev/null +++ b/.github/workflows/publish.yml @@ -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 diff --git a/.gitignore b/.gitignore index 76add87..b512c09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -node_modules -dist \ No newline at end of file +node_modules \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..bf1d0a6 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "tabWidth": 2, + "useTabs": true, + "printWidth": 160 +} diff --git a/build.ts b/build.ts new file mode 100644 index 0000000..72a2fa2 --- /dev/null +++ b/build.ts @@ -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"); +} diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..224f8bd Binary files /dev/null and b/bun.lockb differ diff --git a/dist/index.html b/dist/index.html new file mode 100644 index 0000000..2db6ef7 --- /dev/null +++ b/dist/index.html @@ -0,0 +1,27 @@ + + + + XChaCha20-JS + + +

XChaCha20-JS

+

+
+ + + +
+
+
+ +

Performance Test

+
+ + + +
+

+ + + + diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..47a4d50 --- /dev/null +++ b/dist/index.js @@ -0,0 +1 @@ +class H{keystream;encryptedText;plaintext;nonce;constructor(){this.keystream=[],this.encryptedText=[],this.plaintext=[],this.nonce=[]}rotateleft=(m,E)=>{return m<>>32-E};le32=(m,E,I,f)=>{return(m^E<<8^I<<16^f<<24)>>>0};int2(m){let E=[];for(let I=0;I>>=0,m[I]>>>=0,m[f]>>>=0,m[l]>>>=0}Inner_Block(m){this.Qround(m,0,4,8,12),this.Qround(m,1,5,9,13),this.Qround(m,2,6,10,14),this.Qround(m,3,7,11,15),this.Qround(m,0,5,10,15),this.Qround(m,1,6,11,12),this.Qround(m,2,7,8,13),this.Qround(m,3,4,9,14)}Chacha20_BlockFunction(m,E,I){let f=[];f[0]=1634760805,f[1]=857760878,f[2]=2036477234,f[3]=1797285236,f[4]=this.le32(m[0],m[1],m[2],m[3]),f[5]=this.le32(m[4],m[5],m[6],m[7]),f[6]=this.le32(m[8],9,m[10],m[11]),f[7]=this.le32(m[12],m[13],m[14],m[15]),f[8]=this.le32(m[16],m[17],18,m[19]),f[9]=this.le32(m[20],m[21],22,m[23]),f[10]=this.le32(m[24],m[25],m[26],m[27]),f[11]=this.le32(m[28],m[29],m[30],m[31]),f[12]=I,f[13]=this.le32(E[0],E[1],E[2],E[3]),f[14]=this.le32(E[4],E[5],E[6],E[7]),f[15]=this.le32(E[8],E[9],E[10],E[11]);let l=new Uint32Array(f.slice());for(let L=1;L<=10;L++)this.Inner_Block(l);let v=[];for(let L=0,u=0;L<16;L++)f[L]+=l[L],v[u++]=f[L]&255,v[u++]=f[L]>>>8&255,v[u++]=f[L]>>>16&255,v[u++]=f[L]>>>24&255;return v}HChacha20_BlockFunction(m,E){let I=[];I[0]=1634760805,I[1]=857760878,I[2]=2036477234,I[3]=1797285236,I[4]=this.le32(m[0],m[1],m[2],m[3]),I[5]=this.le32(m[4],m[5],m[6],m[7]),I[6]=this.le32(m[8],9,m[10],m[11]),I[7]=this.le32(m[12],m[13],m[14],m[15]),I[8]=this.le32(m[16],m[17],18,m[19]),I[9]=this.le32(m[20],m[21],22,m[23]),I[10]=this.le32(m[24],m[25],m[26],m[27]),I[11]=this.le32(m[28],m[29],m[30],m[31]),I[12]=this.le32(E[0],E[1],E[2],E[3]),I[13]=this.le32(E[4],E[5],E[6],E[7]),I[14]=this.le32(E[8],E[9],E[10],E[11]),I[15]=this.le32(E[12],E[13],E[14],E[15]);let f=new Uint32Array(I.slice());for(let P=1;P<=10;P++)this.Inner_Block(f);let l=f.slice(0,4),v=f.slice(12,16),L=new Uint32Array(l.length+v.length);L.set(l),L.set(v,l.length);let u=[];for(let P=0,M=0;P<8;P++)u[M++]=L[P]&255,u[M++]=L[P]>>>8&255,u[M++]=L[P]>>>16&255,u[M++]=L[P]>>>24&255;return u}chacha20_encrypt(m,E,I,f){let l=[];l.push(...this.Chacha20_BlockFunction(m,I,E));let v=0;for(let u=0;u{let m=V.value,E=j.value;Z.value=H.encrypt(m,E)});document.getElementById("btn-decrypt")?.addEventListener("click",()=>{let m=V.value,E=j.value;Z.value=H.decrypt(m,E)});document.getElementById("btn-start")?.addEventListener("click",()=>{let m=parseInt(q.value,10);if(m<1)m=1;if(m>1e5)m=1e5;const E=document.getElementById("perf");if(!E)return;let I=[],f=[],l=[],v="lTmnm8G6X1ESDuVf1xnf2t1F4XpUZzZYfodPQQbprsx40k3n9d",L=Date.now();E.innerText="1. Performance test has started.\n";let u=Date.now();for(let P=0;P 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 {}; diff --git a/module/xchacha20.js b/module/xchacha20.js new file mode 100644 index 0000000..490783a --- /dev/null +++ b/module/xchacha20.js @@ -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 +}; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 8da6a41..0000000 --- a/package-lock.json +++ /dev/null @@ -1,770 +0,0 @@ -{ - "name": "@rabbit-company/xchacha20", - "version": "1.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@rabbit-company/xchacha20", - "version": "1.1.0", - "license": "MIT", - "devDependencies": { - "@rabbit-company/rabbit-builder": "^1.0.4" - }, - "funding": { - "url": "https://rabbit-company.com/donation" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.11.tgz", - "integrity": "sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.11.tgz", - "integrity": "sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.11.tgz", - "integrity": "sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.11.tgz", - "integrity": "sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.11.tgz", - "integrity": "sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.11.tgz", - "integrity": "sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.11.tgz", - "integrity": "sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.11.tgz", - "integrity": "sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.11.tgz", - "integrity": "sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.11.tgz", - "integrity": "sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.11.tgz", - "integrity": "sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.11.tgz", - "integrity": "sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.11.tgz", - "integrity": "sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.11.tgz", - "integrity": "sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.11.tgz", - "integrity": "sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.11.tgz", - "integrity": "sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.11.tgz", - "integrity": "sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.11.tgz", - "integrity": "sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.11.tgz", - "integrity": "sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.11.tgz", - "integrity": "sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.11.tgz", - "integrity": "sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.11.tgz", - "integrity": "sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@rabbit-company/rabbit-builder": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@rabbit-company/rabbit-builder/-/rabbit-builder-1.0.4.tgz", - "integrity": "sha512-6DnOjUaNl9IwxsqVFgN/E6KQvksho2oUsrtcww9ZINsden/BO0STJ5e3a8pd/esAjx9qzl1ucy6f7JzTPFegvA==", - "dev": true, - "dependencies": { - "chalk": "^5.2.0", - "clean-css": "^5.3.2", - "esbuild": "^0.17.11", - "fs-extra": "^11.1.0", - "glob": "^9.2.1", - "html-minifier": "^4.0.0", - "multi-progress-bars": "^5.0.3" - }, - "bin": { - "rabbit-builder": "bin/index.mjs" - }, - "funding": { - "url": "https://rabbit-company.com/donation" - } - }, - "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/camel-case": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", - "dev": true, - "dependencies": { - "no-case": "^2.2.0", - "upper-case": "^1.1.1" - } - }, - "node_modules/chalk": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", - "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/clean-css": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", - "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==", - "dev": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/esbuild": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.11.tgz", - "integrity": "sha512-pAMImyokbWDtnA/ufPxjQg0fYo2DDuzAlqwnDvbXqHLphe+m80eF++perYKVm8LeTuj2zUuFXC+xgSVxyoHUdg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.17.11", - "@esbuild/android-arm64": "0.17.11", - "@esbuild/android-x64": "0.17.11", - "@esbuild/darwin-arm64": "0.17.11", - "@esbuild/darwin-x64": "0.17.11", - "@esbuild/freebsd-arm64": "0.17.11", - "@esbuild/freebsd-x64": "0.17.11", - "@esbuild/linux-arm": "0.17.11", - "@esbuild/linux-arm64": "0.17.11", - "@esbuild/linux-ia32": "0.17.11", - "@esbuild/linux-loong64": "0.17.11", - "@esbuild/linux-mips64el": "0.17.11", - "@esbuild/linux-ppc64": "0.17.11", - "@esbuild/linux-riscv64": "0.17.11", - "@esbuild/linux-s390x": "0.17.11", - "@esbuild/linux-x64": "0.17.11", - "@esbuild/netbsd-x64": "0.17.11", - "@esbuild/openbsd-x64": "0.17.11", - "@esbuild/sunos-x64": "0.17.11", - "@esbuild/win32-arm64": "0.17.11", - "@esbuild/win32-ia32": "0.17.11", - "@esbuild/win32-x64": "0.17.11" - } - }, - "node_modules/fs-extra": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", - "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/glob": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.2.1.tgz", - "integrity": "sha512-Pxxgq3W0HyA3XUvSXcFhRSs+43Jsx0ddxcFrbjxNGkL2Ak5BAUBxLqI5G6ADDeCHLfzzXFhe0b1yYcctGmytMA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^7.4.1", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/html-minifier": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", - "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", - "dev": true, - "dependencies": { - "camel-case": "^3.0.0", - "clean-css": "^4.2.1", - "commander": "^2.19.0", - "he": "^1.2.0", - "param-case": "^2.1.1", - "relateurl": "^0.2.7", - "uglify-js": "^3.5.1" - }, - "bin": { - "html-minifier": "cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/html-minifier/node_modules/clean-css": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", - "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", - "dev": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/lower-case": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/minimatch": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.2.tgz", - "integrity": "sha512-xy4q7wou3vUoC9k1xGTXc+awNdGaGVHtFUaey8tiX4H1QRc04DZ/rmDFwNm2EBsuYEhAZ6SgMmYf3InGY6OauA==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz", - "integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/multi-progress-bars": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/multi-progress-bars/-/multi-progress-bars-5.0.3.tgz", - "integrity": "sha512-/EzDF3NVeYTFLoLQ33fgvM6933rS2MZlEPWBivsc94Y/lj2xpWOs/DpJrCG+XrPOo4/P6DW+oxZyKnRKw0d/nQ==", - "dev": true, - "dependencies": { - "chalk": "^5.2.0", - "string-width": "5.1.2", - "strip-ansi": "7.0.1" - }, - "engines": { - "node": ">14.18.0" - } - }, - "node_modules/no-case": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", - "dev": true, - "dependencies": { - "lower-case": "^1.1.1" - } - }, - "node_modules/param-case": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", - "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", - "dev": true, - "dependencies": { - "no-case": "^2.2.0" - } - }, - "node_modules/path-scurry": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.1.tgz", - "integrity": "sha512-OW+5s+7cw6253Q4E+8qQ/u1fVvcJQCJo/VFD8pje+dbJCF1n5ZRMV2AEHbGp+5Q7jxQIYJxkHopnj6nzdGeZLA==", - "dev": true, - "dependencies": { - "lru-cache": "^7.14.1", - "minipass": "^4.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/upper-case": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", - "dev": true - } - } -} diff --git a/package.json b/package.json index 18b72d2..10102ef 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "@rabbit-company/xchacha20", - "version": "1.1.0", + "version": "2.1.0", "description": "XChaCha20 encryption", - "main": "./src/xchacha20.js", + "main": "./module/xchacha20.js", + "browser": "./module/xchacha20.js", "type": "module", "homepage": "https://github.com/Rabbit-Company/XChaCha20-JS", "funding": "https://rabbit-company.com/donation", @@ -10,10 +11,11 @@ "license": "MIT", "private": false, "scripts": { - "build": "rabbit-builder" + "build": "bun run build.ts" }, "files": [ - "src/xchacha20.js" + "module/xchacha20.js", + "module/xchacha20.d.ts" ], "repository": { "type": "git", @@ -34,6 +36,11 @@ "security" ], "devDependencies": { - "@rabbit-company/rabbit-builder": "^1.0.4" + "@types/bun": "latest", + "bun-plugin-dts": "^0.2.3", + "@rabbit-company/logger": "^4.0.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" } -} \ No newline at end of file +} diff --git a/rabbit-builder.json b/rabbit-builder.json deleted file mode 100644 index 953f2a4..0000000 --- a/rabbit-builder.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "code": { - "location": "src" - }, - "tasks": { - "default": { - "location": "dist", - "output": ".", - "execute": { - "copy": { - "action": "copy", - "location": "../src" - }, - "minifyHTML": { - "action": "minifyHTML" - }, - "minifyJS": { - "action": "minifyJS", - "sourceMap": "linked", - "bundle": {} - } - } - } - } -} \ No newline at end of file diff --git a/src/index.html b/src/index.html index bd16e82..2db6ef7 100644 --- a/src/index.html +++ b/src/index.html @@ -5,17 +5,18 @@

XChaCha20-JS

-

+

-

-
+ +
+

Performance Test

- +
@@ -23,4 +24,4 @@

Performance Test

- \ No newline at end of file + diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 0f9ec21..0000000 --- a/src/index.js +++ /dev/null @@ -1,55 +0,0 @@ -import XChaCha20 from "./xchacha20.js"; - -// Encrypt text -document.getElementById("btn-encrypt").addEventListener('click', () => { - let textPlan = document.getElementById("text-1").value; - let secretKey = document.getElementById("secretKey").value; - - document.getElementById("text-2").value = XChaCha20.encrypt(textPlan, secretKey); -}); - -// Decrypt text -document.getElementById("btn-decrypt").addEventListener('click', () => { - let textEncrypted = document.getElementById("text-1").value; - let secretKey = document.getElementById("secretKey").value; - - document.getElementById("text-2").value = XChaCha20.decrypt(textEncrypted, secretKey); -}); - -function calcT(timer){ - return Date.now() - timer; -} - -// Performance test -document.getElementById("btn-start").addEventListener("click", () => { - let amount = document.getElementById("amount").value; - if(amount < 1) amount = 1; - if(amount > 100000) amount = 100000; - let perf = document.getElementById("perf"); - let messages = []; - let encryptedMessages = []; - let secretKey = btoa(XChaCha20.convertToText(XChaCha20.randomNonce())) + btoa(XChaCha20.convertToText(XChaCha20.randomNonce())); - let timerStart = Date.now(); - - perf.innerText = "1. Performance test has started.\n"; - - let timer = Date.now(); - for(let i = 0; i < amount; i++){ - messages[i] = btoa(XChaCha20.convertToText(XChaCha20.randomNonce())); - } - perf.innerText += "2. " + amount + " random messages generated in " + calcT(timer) + " milliseconds.\n"; - - timer = Date.now(); - for(let i = 0; i < amount; i++){ - encryptedMessages[i] = XChaCha20.encrypt(messages[i], secretKey); - } - perf.innerText += "3. " + amount + " random messages encrypted in " + calcT(timer) + " milliseconds.\n"; - - timer = Date.now(); - for(let i = 0; i < amount; i++){ - XChaCha20.decrypt(encryptedMessages[i], secretKey); - } - perf.innerText += "4. " + amount + " random messages decrypted in " + calcT(timer) + " milliseconds.\n"; - - perf.innerText += "5. Performance test has completed in " + calcT(timerStart) + " milliseconds.\n"; -}); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..f0aa10d --- /dev/null +++ b/src/index.ts @@ -0,0 +1,74 @@ +import XChaCha20 from "./xchacha20"; + +const text1Input = document.getElementById("text-1") as HTMLInputElement; +const text2Input = document.getElementById("text-2") as HTMLInputElement; +const secretKeyInput = document.getElementById("secretKey") as HTMLInputElement; +const amountInput = document.getElementById("amount") as HTMLInputElement; + +// Encrypt text +document.getElementById("btn-encrypt")?.addEventListener("click", () => { + let textPlan = text1Input.value; + let secretKey = secretKeyInput.value; + + text2Input.value = XChaCha20.encrypt(textPlan, secretKey); +}); + +// Decrypt text +document.getElementById("btn-decrypt")?.addEventListener("click", () => { + let textEncrypted = text1Input.value; + let secretKey = secretKeyInput.value; + + text2Input.value = XChaCha20.decrypt(textEncrypted, secretKey); +}); + +function generateRandomText(length: number) { + let result = ""; + let charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for (let i = 0; i < length; i++) { + const randomIndex = Math.floor(Math.random() * charset.length); + result += charset.charAt(randomIndex); + } + + return result; +} + +function calcT(timer: number) { + return Date.now() - timer; +} + +// Performance test +document.getElementById("btn-start")?.addEventListener("click", () => { + let amount = parseInt(amountInput.value, 10); + if (amount < 1) amount = 1; + if (amount > 100000) amount = 100000; + const perf = document.getElementById("perf"); + if (!perf) return; + let messages = []; + let encryptedMessages = []; + let decryptedMessages = []; + let secretKey = "lTmnm8G6X1ESDuVf1xnf2t1F4XpUZzZYfodPQQbprsx40k3n9d"; + let timerStart = Date.now(); + + perf.innerText = "1. Performance test has started.\n"; + + let timer = Date.now(); + for (let i = 0; i < amount; i++) { + messages[i] = generateRandomText(30); + } + perf.innerText += "2. " + amount + " random messages generated in " + calcT(timer) + " milliseconds.\n"; + + timer = Date.now(); + for (let i = 0; i < amount; i++) { + encryptedMessages[i] = XChaCha20.encrypt(messages[i], secretKey); + } + perf.innerText += "3. " + amount + " random messages encrypted in " + calcT(timer) + " milliseconds.\n"; + + timer = Date.now(); + for (let i = 0; i < amount; i++) { + decryptedMessages[i] = XChaCha20.decrypt(encryptedMessages[i], secretKey); + } + perf.innerText += "4. " + amount + " random messages decrypted in " + calcT(timer) + " milliseconds.\n"; + + perf.innerText += "5. Performance test has completed in " + calcT(timerStart) + " milliseconds.\n"; +}); diff --git a/src/xchacha20.js b/src/xchacha20.js deleted file mode 100644 index 196f37e..0000000 --- a/src/xchacha20.js +++ /dev/null @@ -1,258 +0,0 @@ -export default class XChaCha20{ - - constructor(){ - this.keystream = NaN; - this.encryptedText = NaN; - this.plaintext = NaN; - this.nonce = NaN; - } - - 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){ - var hex, i; - - let result = []; - for (i = 0; i < data.length; i++) { - 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){ - // column_QuarterRounds - 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); - // diagonal_QuarterRounds - 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 = []; - //Constant - state[0] = 0x61707865; - state[1] = 0x3320646e; - state[2] = 0x79622d32; - state[3] = 0x6b206574; - //Key - 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]); - //Counter - state[12] = block_counter; - //Nonce - 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] & 0xFF; - Serialized_Block[i2++] = (state[i] >>> 8) & 0xFF; - Serialized_Block[i2++] = (state[i] >>> 16) & 0xFF; - Serialized_Block[i2++] = (state[i] >>> 24) & 0xFF; - } - return Serialized_Block; - } - - HChacha20_BlockFunction(key, nonce){ - let state = []; - //Constant - state[0] = 0x61707865; - state[1] = 0x3320646e; - state[2] = 0x79622d32; - state[3] = 0x6b206574; - //Key - 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]); - - //Nonce - 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] & 0xFF; - Serialized_Block[i2++] = (B[i] >>> 8) & 0xFF; - Serialized_Block[i2++] = (B[i] >>> 16) & 0xFF; - Serialized_Block[i2++] = (B[i] >>> 24) & 0xFF; - } - 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],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 = [0x00, 0x00, 0x00, 0x00]; - 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 = [0x00, 0x00, 0x00, 0x00]; - 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){ - var hex, i; - - let result = []; - for (i = 0; i < data.length; i++) { - hex = data.charCodeAt(i).toString(16); - result.push(hex); - } - - return result - } - - static b64EncodeUnicode(str) { - return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, - function toSolidBytes(match, p1) { - return String.fromCharCode('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(){ - var rand_n = new Uint8Array(24); - crypto.getRandomValues(rand_n); - return rand_n; - } - - static encrypt(message, secretKey){ - message = XChaCha20.hexEncode(message); - secretKey = XChaCha20.hexEncode(secretKey); - let nonce = XChaCha20.randomNonce(); - - let e1 = new XChaCha20(); - e1.xchacha20_encrypt(secretKey, nonce, message); - e1.encryptedText.push(...(nonce)); - e1.encryptedText = XChaCha20.b64EncodeUnicode(XChaCha20.convertToText(e1.encryptedText)); - - return e1.encryptedText; - } - - static decrypt(message, secretKey){ - secretKey = XChaCha20.hexEncode(secretKey); - - let d1 = new XChaCha20(); - d1.xchacha20_decrypt(secretKey, message); - - return XChaCha20.convertToText(d1.plaintext).replace('\x00',''); - } -} \ No newline at end of file diff --git a/src/xchacha20.ts b/src/xchacha20.ts new file mode 100644 index 0000000..e33aa94 --- /dev/null +++ b/src/xchacha20.ts @@ -0,0 +1,294 @@ +export default class XChaCha20 { + keystream: number[]; + encryptedText: number[]; + plaintext: number[]; + nonce: number[]; + + constructor() { + this.keystream = []; + this.encryptedText = []; + this.plaintext = []; + this.nonce = []; + } + + rotateleft = (a: number, b: number): number => { + return (a << b) | (a >>> (32 - b)); + }; + + le32 = (a: number, b: number, c: number, d: number): number => { + return (a ^ (b << 8) ^ (c << 16) ^ (d << 24)) >>> 0; + }; + + int2(data: string): number[] { + let result: number[] = []; + for (let i = 0; i < data.length; i++) { + const hex = data.charCodeAt(i).toString(16); + result.push(parseInt(hex, 16)); + } + return result; + } + + Qround(state: Uint32Array, a: number, b: number, c: number, d: number): void { + 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: Uint32Array): void { + // column_QuarterRounds + 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); + // diagonal_QuarterRounds + 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: number[], nonce: number[], block_counter: number): number[] { + let state: number[] = []; + // Constant + state[0] = 0x61707865; + state[1] = 0x3320646e; + state[2] = 0x79622d32; + state[3] = 0x6b206574; + // Key + 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]); + // Counter + state[12] = block_counter; + // Nonce + 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: number[] = []; + for (let i = 0, i2 = 0; i < 16; i++) { + state[i] += temp[i]; + + Serialized_Block[i2++] = state[i] & 0xff; + Serialized_Block[i2++] = (state[i] >>> 8) & 0xff; + Serialized_Block[i2++] = (state[i] >>> 16) & 0xff; + Serialized_Block[i2++] = (state[i] >>> 24) & 0xff; + } + return Serialized_Block; + } + + HChacha20_BlockFunction(key: number[], nonce: number[]): number[] { + let state: number[] = []; + // Constant + state[0] = 0x61707865; + state[1] = 0x3320646e; + state[2] = 0x79622d32; + state[3] = 0x6b206574; + // Key + 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]); + + // Nonce + 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: number[] = []; + for (let i = 0, i2 = 0; i < 8; i++) { + Serialized_Block[i2++] = B[i] & 0xff; + Serialized_Block[i2++] = (B[i] >>> 8) & 0xff; + Serialized_Block[i2++] = (B[i] >>> 16) & 0xff; + Serialized_Block[i2++] = (B[i] >>> 24) & 0xff; + } + return Serialized_Block; + } + + chacha20_encrypt(key: number[], counter: number, nonce: number[], plaintext: number[]): void { + let keystream: number[] = []; + 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: number[] = []; + for (let i = 0; i < plaintext.length; i++) { + cipherText[i] = plaintext[i] ^ keystream[i]; + } + this.keystream = keystream; + this.encryptedText = cipherText; + } + + chacha20_decrypt(key: number[], counter: number, nonce: number[], eT: number[]): void { + let keystream: number[] = []; + 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: number[] = []; + for (let i = 0; i < this.encryptedText.length; i++) { + pT[i] = this.encryptedText[i] ^ keystream[i]; + } + this.plaintext = pT; + } + + xchacha20_decrypt(key: number[], encryptedText: string): void { + 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 = [0x00, 0x00, 0x00, 0x00]; + chacha20_nonce.push(...nonce.slice(16, 24)); + this.chacha20_decrypt(subkey, 0, chacha20_nonce, this.encryptedText); + } + + xchacha20_encrypt(key: number[], nonce: number[], plaintext: number[]): void { + let subkey = this.HChacha20_BlockFunction(key, nonce.slice(0, 16)); + let chacha20_nonce = [0x00, 0x00, 0x00, 0x00]; + chacha20_nonce.push(...nonce.slice(16, 24)); + this.chacha20_encrypt(subkey, 0, chacha20_nonce, plaintext); + } + + private static convertToText(data: number[]): string { + let text = ""; + for (let i = 0; i < data.length; i++) { + text += String.fromCharCode(data[i]); + } + return text; + } + + private static hexEncode(data: string): any[] { + let result: string[] = []; + for (let i = 0; i < data.length; i++) { + result.push(data.charCodeAt(i).toString(16)); + } + return result; + } + + private static b64EncodeUnicode(str: string): string { + return btoa( + encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function toSolidBytes(match, p1) { + return String.fromCharCode(Number("0x" + p1)); + }) + ); + } + + private static b64DecodeUnicode(str: string): string { + return decodeURIComponent( + atob(str) + .split("") + .map(function (c) { + return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); + }) + .join("") + ); + } + + private static randomNonce(): Uint8Array { + let rand_n = new Uint8Array(24); + crypto.getRandomValues(rand_n); + return rand_n; + } + + /** + * 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 { + 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)); + } + + /** + * 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 { + const hexSecretKey = XChaCha20.hexEncode(secretKey); + + let d1 = new XChaCha20(); + d1.xchacha20_decrypt(hexSecretKey, message); + + return XChaCha20.convertToText(d1.plaintext); + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..245b050 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + /* Linting */ + "skipLibCheck": true, + "strict": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true + } +}