diff --git a/src/controllers/keystore/keystore.ts b/src/controllers/keystore/keystore.ts index 529f0b035..165fd7b34 100644 --- a/src/controllers/keystore/keystore.ts +++ b/src/controllers/keystore/keystore.ts @@ -9,16 +9,7 @@ import { encryptWithPublicKey, publicKeyByPrivateKey } from 'eth-crypto' -import { - concat, - getBytes, - hexlify, - keccak256, - Mnemonic, - randomBytes, - toUtf8Bytes, - Wallet -} from 'ethers' +import { concat, getBytes, hexlify, keccak256, Mnemonic, toUtf8Bytes, Wallet } from 'ethers' import scrypt from 'scrypt-js' import EmittableError from '../../classes/EmittableError' @@ -38,6 +29,7 @@ import { } from '../../interfaces/keystore' import { Storage } from '../../interfaces/storage' import { WindowManager } from '../../interfaces/window' +import { EntropyGenerator } from '../../libs/entropyGenerator/entropyGenerator' import { getDefaultKeyLabel, getShouldMigrateKeyMetaNullToKeyMetaCreatedAt, @@ -301,16 +293,14 @@ export class KeystoreController extends EventEmitter { }) let mainKey: MainKey | null = this.#mainKey + const entropyGenerator = new EntropyGenerator() + // We are not unlocked if (!mainKey) { if (!this.#keystoreSecrets.length) { - const key = getBytes(keccak256(concat([randomBytes(32), toUtf8Bytes(extraEntropy)]))).slice( - 0, - 16 - ) mainKey = { - key, - iv: randomBytes(16) + key: entropyGenerator.generateRandomBytes(16, extraEntropy), + iv: entropyGenerator.generateRandomBytes(16, extraEntropy) } } else throw new EmittableError({ @@ -324,7 +314,7 @@ export class KeystoreController extends EventEmitter { } } - const salt = randomBytes(32) + const salt = entropyGenerator.generateRandomBytes(32, extraEntropy) const key = await scrypt.scrypt( getBytesForSecret(secret), salt, @@ -334,7 +324,7 @@ export class KeystoreController extends EventEmitter { scryptDefaults.dkLen, () => {} ) - const iv = randomBytes(16) + const iv = entropyGenerator.generateRandomBytes(16, extraEntropy) const derivedKey = key.slice(0, 16) const macPrefix = key.slice(16, 32) const counter = new aes.Counter(iv) diff --git a/src/libs/entropyGenerator/entropyGenerator.ts b/src/libs/entropyGenerator/entropyGenerator.ts new file mode 100644 index 000000000..e103b94d7 --- /dev/null +++ b/src/libs/entropyGenerator/entropyGenerator.ts @@ -0,0 +1,54 @@ +/* eslint-disable no-bitwise */ +import { getBytes, keccak256, randomBytes } from 'ethers' + +export class EntropyGenerator { + #entropyPool: Uint8Array = new Uint8Array(0) + + generateRandomBytes(length: number, extraEntropy: string): Uint8Array { + this.#resetEntropyPool() + this.#collectSystemNoiseEntropy() + this.#collectTimeEntropy() + + if (extraEntropy) { + const encoder = new TextEncoder() + const uint8Array = encoder.encode(extraEntropy) + this.addEntropy(uint8Array) + } + + if (this.#entropyPool.length === 0) throw new Error('Entropy pool is empty') + + const hash = getBytes(keccak256(this.#entropyPool)) + const randomBytesGenerated = randomBytes(length) + // ensures non-deterministic final output + for (let i = 0; i < length; i++) { + randomBytesGenerated[i] ^= hash[i % hash.length] + } + + return randomBytesGenerated + } + + #collectTimeEntropy(): void { + const now = performance.now() + + if (!now) return + + const timeEntropy = new Uint8Array(new Float64Array([now]).buffer) + this.addEntropy(timeEntropy) + } + + #collectSystemNoiseEntropy(): void { + const systemNoise = randomBytes(16) + this.addEntropy(systemNoise) + } + + addEntropy(newEntropy: Uint8Array): void { + const combined = new Uint8Array(this.#entropyPool.length + newEntropy.length) + combined.set(this.#entropyPool) + combined.set(newEntropy, this.#entropyPool.length) + this.#entropyPool = combined + } + + #resetEntropyPool() { + this.#entropyPool = new Uint8Array(0) + } +}