rwadurian/tools/mnemonic-test/node_modules/hash-wasm/lib/scrypt.ts

142 lines
3.6 KiB
TypeScript

import wasmJson from "../wasm/scrypt.wasm.json";
import { WASMInterface } from "./WASMInterface";
import { pbkdf2 } from "./pbkdf2";
import { createSHA256 } from "./sha256";
import { type IDataType, getDigestHex } from "./util";
export interface ScryptOptions {
/**
* Password (or message) to be hashed
*/
password: IDataType;
/**
* Salt (usually containing random bytes)
*/
salt: IDataType;
/**
* CPU / memory cost - must be a power of 2 (e.g. 1024)
*/
costFactor: number;
/**
* Block size (8 is commonly used)
*/
blockSize: number;
/**
* Degree of parallelism
*/
parallelism: number;
/**
* Output size in bytes
*/
hashLength: number;
/**
* Output data type. Defaults to hexadecimal string
*/
outputType?: "hex" | "binary";
}
async function scryptInternal(
options: ScryptOptions,
): Promise<string | Uint8Array> {
const { costFactor, blockSize, parallelism, hashLength } = options;
const SHA256Hasher = createSHA256();
const blockData = await pbkdf2({
password: options.password,
salt: options.salt,
iterations: 1,
hashLength: 128 * blockSize * parallelism,
hashFunction: SHA256Hasher,
outputType: "binary",
});
const scryptInterface = await WASMInterface(wasmJson, 0);
// last block is for storing the temporary vectors
const VSize = 128 * blockSize * costFactor;
const XYSize = 256 * blockSize;
scryptInterface.setMemorySize(blockData.length + VSize + XYSize);
scryptInterface.writeMemory(blockData, 0);
// mix blocks
scryptInterface.getExports().scrypt(blockSize, costFactor, parallelism);
const expensiveSalt = scryptInterface
.getMemory()
.subarray(0, 128 * blockSize * parallelism);
const outputData = await pbkdf2({
password: options.password,
salt: expensiveSalt,
iterations: 1,
hashLength,
hashFunction: SHA256Hasher,
outputType: "binary",
});
if (options.outputType === "hex") {
const digestChars = new Uint8Array(hashLength * 2);
return getDigestHex(digestChars, outputData, hashLength);
}
// return binary format
return outputData;
}
const isPowerOfTwo = (v: number): boolean => v && !(v & (v - 1));
const validateOptions = (options: ScryptOptions) => {
if (!options || typeof options !== "object") {
throw new Error("Invalid options parameter. It requires an object.");
}
if (!Number.isInteger(options.blockSize) || options.blockSize < 1) {
throw new Error("Block size should be a positive number");
}
if (
!Number.isInteger(options.costFactor) ||
options.costFactor < 2 ||
!isPowerOfTwo(options.costFactor)
) {
throw new Error("Cost factor should be a power of 2, greater than 1");
}
if (!Number.isInteger(options.parallelism) || options.parallelism < 1) {
throw new Error("Parallelism should be a positive number");
}
if (!Number.isInteger(options.hashLength) || options.hashLength < 1) {
throw new Error("Hash length should be a positive number.");
}
if (options.outputType === undefined) {
options.outputType = "hex";
}
if (!["hex", "binary"].includes(options.outputType)) {
throw new Error(
`Insupported output type ${options.outputType}. Valid values: ['hex', 'binary']`,
);
}
};
interface IScryptOptionsBinary {
outputType: "binary";
}
type ScryptReturnType<T> = T extends IScryptOptionsBinary ? Uint8Array : string;
/**
* Calculates hash using the scrypt password-based key derivation function
* @returns Computed hash as a hexadecimal string or as
* Uint8Array depending on the outputType option
*/
export async function scrypt<T extends ScryptOptions>(
options: T,
): Promise<ScryptReturnType<T>> {
validateOptions(options);
return scryptInternal(options) as Promise<ScryptReturnType<T>>;
}