-
Notifications
You must be signed in to change notification settings - Fork 50
/
Copy pathscrypt.ts
141 lines (119 loc) · 3.6 KB
/
scrypt.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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>>;
}