1 import { uint8ArrayToBase64String } from '../helpers/encoding';
3 const Pbkdf2PRFKeySize = 32;
4 const Pbkdf2ChallengeSize = 3 * Pbkdf2PRFKeySize + 32 + 4;
5 const Pbkdf2OutputSize = 32;
7 function areBuffersEqual(buf1: ArrayBuffer, buf2: ArrayBuffer): boolean {
8 if (buf1.byteLength !== buf2.byteLength) {
11 const dv1 = new Uint8Array(buf1);
12 const dv2 = new Uint8Array(buf2);
13 for (let i = 0; i !== buf1.byteLength; i++) {
14 if (dv1[i] !== dv2[i]) {
20 async function solveChallengePbkdf2Preimage(b64challenge: string, deadlineUnixMilli: number) {
21 const buffer = new ArrayBuffer(8);
22 const challenge = Uint8Array.from(atob(b64challenge), (c) => c.charCodeAt(0));
23 if (challenge.length !== Pbkdf2ChallengeSize) {
24 throw new Error('Invalid challenge length');
26 const prfKeys = challenge.subarray(0, 3 * Pbkdf2PRFKeySize);
27 const goal = challenge.subarray(3 * Pbkdf2PRFKeySize, 3 * Pbkdf2PRFKeySize + sha256Size);
28 const pbkdf2Params = challenge.subarray(3 * Pbkdf2PRFKeySize + sha256Size, Pbkdf2ChallengeSize);
29 const iterations = new DataView(pbkdf2Params.buffer, pbkdf2Params.byteOffset, pbkdf2Params.byteLength).getUint32(
33 const startTime = Date.now();
34 let stage: ArrayBuffer = new ArrayBuffer(0);
36 const prePRFKey = await crypto.subtle.importKey(
38 prfKeys.subarray(0, Pbkdf2PRFKeySize),
39 { name: 'HMAC', hash: 'SHA-256' },
43 const postPRFKey = await crypto.subtle.importKey(
45 prfKeys.subarray(2 * Pbkdf2PRFKeySize),
46 { name: 'HMAC', hash: 'SHA-256' },
50 const salt = prfKeys.subarray(Pbkdf2PRFKeySize, 2 * Pbkdf2PRFKeySize);
52 if (Date.now() - startTime > deadlineUnixMilli) {
53 throw new Error('Operation timed out');
55 new DataView(buffer).setUint32(0, i, true);
56 const prePRFHash = await crypto.subtle.sign('HMAC', prePRFKey, buffer);
57 const derivedKey = await crypto.subtle.importKey('raw', prePRFHash, 'PBKDF2', false, ['deriveBits']);
58 stage = await crypto.subtle.deriveBits(
62 iterations: iterations,
68 const postPRFHash = await crypto.subtle.sign('HMAC', postPRFKey, stage);
69 if (areBuffersEqual(postPRFHash, goal)) {
74 let solution = new Uint8Array(buffer.byteLength + stage.byteLength);
75 solution.set(new Uint8Array(buffer), 0);
76 solution.set(new Uint8Array(stage), buffer.byteLength);
78 const duration = Date.now() - startTime;
79 return { solution, duration };
82 self.onmessage = async function (event) {
83 const { b64Source } = event.data;
85 const result = await solveChallengePbkdf2Preimage(b64Source, 15000);
86 const b64 = uint8ArrayToBase64String(result.solution);
87 self.postMessage(b64 + `, ${result.duration}`);