diff --git a/package.json b/package.json index 4fac9b7..1545313 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,8 @@ "@types/chrome": "^0.0.263", "asmcrypto.js": "2.3.2", "axios": "^1.7.7", - "bcryptjs": "2.4.3", "buffer": "6.0.3", + "bcryptjs": "2.4.3", "chokidar": "^3.6.0", "compressorjs": "^1.2.1", "cordova-plugin-android-permissions": "^1.1.5", diff --git a/src/deps/bcryptworker.worker.js b/src/deps/bcryptworker.worker.js new file mode 100644 index 0000000..d6d048a --- /dev/null +++ b/src/deps/bcryptworker.worker.js @@ -0,0 +1,12 @@ +import bcrypt from 'bcryptjs' + + +self.onmessage = function (e) { + const { hashBase64, salt } = e.data; + try { + const result = bcrypt.hashSync(hashBase64, salt); + self.postMessage({ result }); + } catch (error) { + self.postMessage({ error: error.message }); + } +}; \ No newline at end of file diff --git a/src/deps/bcryptworkerwasm.worker.js b/src/deps/bcryptworkerwasm.worker.js new file mode 100644 index 0000000..ab5a733 --- /dev/null +++ b/src/deps/bcryptworkerwasm.worker.js @@ -0,0 +1,71 @@ +import { bcrypt } from 'hash-wasm'; + +self.onmessage = async function (e) { + const { hashBase64, salt } = e.data; + + try { + // Split the salt string + const parts = salt.split('$'); + if (parts.length !== 4) { + throw new Error('Invalid salt format'); + } + + // Extract the raw salt from the bcrypt salt string + const rawSalt = parts[3]; // e.g., "IxVE941tXVUD4cW0TNVm.O" + + // Bcrypt's custom Base64 decoding function + function bcryptBase64Decode(base64String) { + const base64Code = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const inverseBase64Code = {}; + for (let i = 0; i < base64Code.length; i++) { + inverseBase64Code[base64Code[i]] = i; + } + const bytes = []; + let i = 0; + while (i < base64String.length) { + const c1 = inverseBase64Code[base64String.charAt(i++)]; + const c2 = inverseBase64Code[base64String.charAt(i++)]; + bytes.push(((c1 << 2) | (c2 >> 4)) & 0xff); + + if (i >= base64String.length) break; + + const c3 = inverseBase64Code[base64String.charAt(i++)]; + bytes.push(((c2 << 4) | (c3 >> 2)) & 0xff); + + if (i >= base64String.length) break; + + const c4 = inverseBase64Code[base64String.charAt(i++)]; + bytes.push(((c3 << 6) | c4) & 0xff); + } + return new Uint8Array(bytes); + } + + // Decode the bcrypt Base64 salt into a Uint8Array + const saltArray = bcryptBase64Decode(rawSalt); + + if (saltArray.length !== 16) { + throw new Error('Salt must be 16 bytes long for hash-wasm.'); + } + + // Extract the cost factor + const costFactor = parseInt(parts[2], 10); + + // Determine if 'hashBase64' is the password or needs decoding + const password = hashBase64; // Adjust if 'hashBase64' is encoded + + // Compute the hash using hash-wasm + const result = await bcrypt({ + password: password, + salt: saltArray, + costFactor: costFactor, + outputType: 'encoded', // Outputs in bcrypt format + }); + + + // Return the result to the main thread + self.postMessage({ result }); + } catch (error) { + console.error('Error in Web Worker:', error); + self.postMessage({ error: error.message }); + } +}; diff --git a/src/deps/kdf.ts b/src/deps/kdf.ts index 98bdc36..6844d67 100644 --- a/src/deps/kdf.ts +++ b/src/deps/kdf.ts @@ -1,9 +1,10 @@ // @ts-nocheck import {bytes_to_base64 as bytesToBase64, Sha512} from 'asmcrypto.js' -import bcrypt from 'bcryptjs' import utils from '../utils/utils' -import { crypto } from '../constants/decryptWallet' +import { crypto as crypto2 } from '../constants/decryptWallet' +import BcryptWorker from './bcryptworker.worker.js?worker'; + const stringtoUTF8Array = (message)=> { if (typeof message === 'string') { var s = unescape(encodeURIComponent(message)) // UTF-8 @@ -15,6 +16,29 @@ const stringtoUTF8Array = (message)=> { return message } + + +const bcryptInWorker = (hashBase64, salt) => { + return new Promise((resolve, reject) => { + const worker = new BcryptWorker() + worker.onmessage = (e) => { + const { result, error } = e.data; + if (error) { + reject(error); + } else { + resolve(result); + } + worker.terminate(); + }; + worker.onerror = (err) => { + reject(err.message); + worker.terminate(); + }; + worker.postMessage({ hashBase64, salt }); + }); +}; + + const stringToUTF8Array=(message)=> { if (typeof message !== 'string') return message; // Assuming you still want to pass through non-string inputs unchanged const encoder = new TextEncoder(); // TextEncoder defaults to UTF-8 @@ -23,10 +47,11 @@ const stringToUTF8Array=(message)=> { const computekdf = async (req)=> { const { salt, key, nonce, staticSalt, staticBcryptSalt } = req const combinedBytes = utils.appendBuffer(new Uint8Array([]), stringToUTF8Array(`${staticSalt}${key}${nonce}`)) + const sha512Hash = new Sha512().process(combinedBytes).finish().result const sha512HashBase64 = bytesToBase64(sha512Hash) - const result = bcrypt.hashSync(sha512HashBase64.substring(0, 72), staticBcryptSalt) - + + const result = await bcryptInWorker(sha512HashBase64.substring(0, 72), staticBcryptSalt); return { key, nonce, result } } @@ -55,8 +80,8 @@ export const kdf = async (seed, salt, threads) => { key: seed, salt, nonce, - staticSalt: crypto.staticSalt, - staticBcryptSalt: crypto.staticBcryptSalt + staticSalt: crypto2.staticSalt, + staticBcryptSalt: crypto2.staticBcryptSalt }).then(data => { let jsonData try { @@ -70,6 +95,6 @@ export const kdf = async (seed, salt, threads) => { return data.result }) })) - const result = new Sha512().process(stringtoUTF8Array(crypto.staticSalt + seedParts.reduce((a, c) => a + c))).finish().result + const result = new Sha512().process(stringtoUTF8Array(crypto2.staticSalt + seedParts.reduce((a, c) => a + c))).finish().result return result } diff --git a/vite.config.ts b/vite.config.ts index afd40ab..60fc0d7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; // Import path module for resolving file paths -import { resolve } from 'path'; import fixReactVirtualized from 'esbuild-plugin-react-virtualized' export default defineConfig({ @@ -11,22 +10,8 @@ export default defineConfig({ globals: true, setupFiles: ['./src/test/setup.ts'] }, + plugins: [react()], - build: { - rollupOptions: { - // Specify multiple entry points for Rollup - input: { - index: resolve(__dirname, 'index.html'), // Main entry for your React app - background: resolve(__dirname, 'src/background.ts'), // Separate entry for the background script - }, - output: { - // Adjust the output settings if necessary - entryFileNames: `[name].js`, - chunkFileNames: `[name].js`, - assetFileNames: `[name].[ext]` - } - } - }, optimizeDeps: { esbuildOptions: { plugins: [fixReactVirtualized],