diff --git a/crypto.js b/crypto.js index 9d259b5..39b3291 100644 --- a/crypto.js +++ b/crypto.js @@ -2,23 +2,37 @@ import init, * as ecies from "ecies-wasm"; import { Key } from "./zkl-kds/key"; init(); +const td = new TextDecoder(); +const te = new TextEncoder(); + +const METADATA_LENGTH = 4 + 1020; // [byte length, 255 chars * 4 bytes] /** * Encrypts given data for the recipient * * @param {Key} publicKey The public key of the recipient + * @param {string} fileName Name of the file * @param {Uint8Array} data The plaintext data * @returns {Uint8Array} The ciphertext data * @throws {TypeError} If arguments are of incorrect types */ -export function encryptFile(publicKey, data) { +export function encryptFile(publicKey, fileName, data) { if (!(publicKey instanceof Key)) { throw new TypeError("publicKey must be an instance of Key"); } if (!(data instanceof Uint8Array)) { throw new TypeError("data must be an instance of Uint8Array"); } - return encrypt(publicKey, data); + if (!fileName) + fileName = "Unknown file"; + + const _data = new Uint8Array(METADATA_LENGTH + data.length); + const fnBytes = te.encode(fileName); + _data.set(int2byteArray(fnBytes.length)); + _data.set(fnBytes, 4); + _data.set(data, METADATA_LENGTH); + + return encrypt(publicKey, _data); } /** @@ -26,7 +40,7 @@ export function encryptFile(publicKey, data) { * * @param {Key} privateKey The private key of the recipient * @param {Uint8Array} data The ciphertext data - * @returns {Uint8Array} The plaintext data + * @returns {{data: Uint8Array, fileName: string}} The plaintext data * @throws {TypeError} If arguments are of incorrect types */ export function decryptFile(privateKey, data) { @@ -36,7 +50,17 @@ export function decryptFile(privateKey, data) { if (!(data instanceof Uint8Array)) { throw new TypeError("data must be an instance of Uint8Array"); } - return decrypt(privateKey, data); + + const plaintext = decrypt(privateKey, data); + const fnLength = byteArray2int(plaintext.slice(0, 4)); + const fnBytes = plaintext.slice(4, 4 + fnLength); + const fdBytes = plaintext.slice(METADATA_LENGTH, plaintext.length); + const fileName = td.decode(fnBytes); + + return { + data: fdBytes, + fileName: fileName + }; } /** @@ -55,8 +79,7 @@ export function encryptString(publicKey, string) { throw new TypeError("string must be of type string"); } - const encoder = new TextEncoder(); - const byteArray = encoder.encode(string); + const byteArray = te.encode(string); const encryptedData = encrypt(publicKey, byteArray); return encryptedData.asHexString(); } @@ -84,8 +107,7 @@ export function decryptString(privateKey, string) { string.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)) ); const decryptedData = decrypt(privateKey, byteArray); - const decoder = new TextDecoder(); - return decoder.decode(decryptedData); + return td.decode(decryptedData); } function encrypt(publicKey, plaintext) { @@ -108,6 +130,46 @@ function decrypt(privateKey, ciphertext) { return ecies.decrypt(privateKey.asByteArray, ciphertext); } +/** + * Converts a 32-bit signed integer into an array of 4 bytes (little-endian). + * + * @param {number} int - The 32-bit signed integer to convert. Must be in the range of a signed 32-bit integer (-2^31 to 2^31-1). + * @returns {number[]} An array of 4 bytes, where the least significant byte is the first element (little-endian). + * @example + * // Convert 305419896 (0x12345678) to bytes + * int2byteArray(305419896); // [120, 86, 52, 18] + */ +function int2byteArray(int) { + return [ + int & 0xff ? int & 0xff : 0, + (int >> 8) & 0xff ? (int >> 8) & 0xff : 0, + (int >> 16) & 0xff ? (int >> 16) & 0xff : 0, + (int >> 24) & 0xff ? (int >> 24) & 0xff : 0 + ] +} + +/** + * Converts an array of 4 bytes (little-endian) into a 32-bit signed integer. + * + * @param {number[]} bytes - An array of 4 bytes where the least significant byte is the first element (little-endian). + * @returns {number} The reconstructed 32-bit signed integer. + * @example + * // Convert [120, 86, 52, 18] back to an integer + * byteArray2int([120, 86, 52, 18]); // 305419896 (0x12345678) + */ +function byteArray2int(bytes) { + return (bytes[0]) | + (bytes[1] << 8) | + (bytes[2] << 16) | + (bytes[3] << 24); +} + +function asByteArray() { + return Uint8Array.from( + this.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)) + ); +} + function asHexString() { return this.reduce( (str, byte) => str + byte.toString(16).padStart(2, "0"), diff --git a/index.js b/index.js index afb1d7f..46c0242 100644 --- a/index.js +++ b/index.js @@ -7,9 +7,9 @@ import { } from "./crypto.js"; let keypair; +const el_fileInput = document.querySelector("#fileInput"); -document - .getElementById("fileInput") +el_fileInput .addEventListener("change", function (event) { const file = event.target.files[0]; if (file) { @@ -23,8 +23,9 @@ document return; } - const cipherFile = encryptFile(keypair.pkey, byteArray); - console.log(cipherFile); + console.log('File to be encrypted:', el_fileInput.files[0].name); + const cipherFile = encryptFile(keypair.pkey, el_fileInput.files[0].name, byteArray); + console.log('Ciphertext bytes:', cipherFile); { const numPixels = cipherFile.length / 4; @@ -47,7 +48,9 @@ document } const plainFile = decryptFile(keypair.skey, cipherFile); - console.log(plainFile); + console.log("Decrypted raw data:", plainFile.data); + console.log("Decrypted decoded:", (new TextDecoder()).decode(plainFile.data)); + console.log("Decrypted file name:", plainFile.fileName); }; reader.readAsArrayBuffer(file); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9125f71..7099995 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -130,8 +130,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@22.5.1': - resolution: {integrity: sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==} + '@types/node@22.5.4': + resolution: {integrity: sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==} '@webassemblyjs/ast@1.12.1': resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} @@ -262,8 +262,8 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - caniuse-lite@1.0.30001653: - resolution: {integrity: sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==} + caniuse-lite@1.0.30001660: + resolution: {integrity: sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==} chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -303,8 +303,8 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -315,8 +315,8 @@ packages: ecies-wasm@0.2.0: resolution: {integrity: sha512-T0wkoz2iOu3IN0wugO3gzCn3ADAafA8FRDQUWWST31InPWlUSvH5JH5akegAZw48or1kwQsWE5jGiNqh5woxhg==} - electron-to-chromium@1.5.13: - resolution: {integrity: sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==} + electron-to-chromium@1.5.18: + resolution: {integrity: sha512-1OfuVACu+zKlmjsNdcJuVQuVE61sZOLbNM4JAQ1Rvh6EOj0/EUKhMJjRH73InPlXSh8HIJk1cVZ8pyOV/FMdUQ==} enhanced-resolve@5.17.1: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} @@ -330,8 +330,8 @@ packages: es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} escape-string-regexp@1.0.5: @@ -496,8 +496,8 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -540,8 +540,8 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} @@ -647,8 +647,8 @@ packages: uglify-js: optional: true - terser@5.31.6: - resolution: {integrity: sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==} + terser@5.32.0: + resolution: {integrity: sha512-v3Gtw3IzpBJ0ugkxEX8U0W6+TnPKRRCWGh1jC/iM/e3Ki5+qvO1L1EAZ56bZasc64aXHwRHNIQEzm6//i5cemQ==} engines: {node: '>=10'} hasBin: true @@ -732,7 +732,7 @@ snapshots: '@babel/code-frame@7.24.7': dependencies: '@babel/highlight': 7.24.7 - picocolors: 1.0.1 + picocolors: 1.1.0 '@babel/compat-data@7.25.4': {} @@ -749,7 +749,7 @@ snapshots: '@babel/traverse': 7.25.6 '@babel/types': 7.25.6 convert-source-map: 2.0.0 - debug: 4.3.6 + debug: 4.3.7 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -811,7 +811,7 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.1 + picocolors: 1.1.0 '@babel/parser@7.25.6': dependencies: @@ -830,7 +830,7 @@ snapshots: '@babel/parser': 7.25.6 '@babel/template': 7.25.0 '@babel/types': 7.25.6 - debug: 4.3.6 + debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -869,7 +869,7 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node@22.5.1': + '@types/node@22.5.4': dependencies: undici-types: 6.19.8 @@ -1014,14 +1014,14 @@ snapshots: browserslist@4.23.3: dependencies: - caniuse-lite: 1.0.30001653 - electron-to-chromium: 1.5.13 + caniuse-lite: 1.0.30001660 + electron-to-chromium: 1.5.18 node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) buffer-from@1.1.2: {} - caniuse-lite@1.0.30001653: {} + caniuse-lite@1.0.30001660: {} chalk@2.4.2: dependencies: @@ -1059,13 +1059,13 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - debug@4.3.6: + debug@4.3.7: dependencies: - ms: 2.1.2 + ms: 2.1.3 ecies-wasm@0.2.0: {} - electron-to-chromium@1.5.13: {} + electron-to-chromium@1.5.18: {} enhanced-resolve@5.17.1: dependencies: @@ -1076,7 +1076,7 @@ snapshots: es-module-lexer@1.5.4: {} - escalade@3.1.2: {} + escalade@3.2.0: {} escape-string-regexp@1.0.5: {} @@ -1159,7 +1159,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.5.1 + '@types/node': 22.5.4 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -1199,7 +1199,7 @@ snapshots: dependencies: mime-db: 1.52.0 - ms@2.1.2: {} + ms@2.1.3: {} neo-async@2.6.2: {} @@ -1231,7 +1231,7 @@ snapshots: path-parse@1.0.7: {} - picocolors@1.0.1: {} + picocolors@1.1.0: {} pkg-dir@4.2.0: dependencies: @@ -1321,10 +1321,10 @@ snapshots: jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 - terser: 5.31.6 + terser: 5.32.0 webpack: 5.94.0(webpack-cli@5.1.4) - terser@5.31.6: + terser@5.32.0: dependencies: '@jridgewell/source-map': 0.3.6 acorn: 8.12.1 @@ -1338,8 +1338,8 @@ snapshots: update-browserslist-db@1.1.0(browserslist@4.23.3): dependencies: browserslist: 4.23.3 - escalade: 3.1.2 - picocolors: 1.0.1 + escalade: 3.2.0 + picocolors: 1.1.0 uri-js@4.4.1: dependencies: diff --git a/webpack.config.js b/webpack.config.js index 65278a1..d3dd1d8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,7 +7,7 @@ module.exports = { path: path.resolve(__dirname, "dist"), }, experiments: { - asyncWebAssembly: true, + syncWebAssembly: true, }, mode: "development", };