friendly-challenge
Version:
The client code used for FriendlyCaptcha (widget script, html, styling and webworker solvers)
463 lines (452 loc) • 23.5 kB
JavaScript
(function () {
'use strict';
// Adapted from the base64-arraybuffer package implementation
// (https://github.com/niklasvh/base64-arraybuffer, MIT licensed)
const CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const EQ_CHAR = "=".charCodeAt(0);
// Use a lookup table to find the index.
const lookup = new Uint8Array(256);
for (let i = 0; i < CHARS.length; i++) {
lookup[CHARS.charCodeAt(i)] = i;
}
function decode(base64) {
const len = base64.length;
let bufferLength = (len * 3) >>> 2; // * 0.75
if (base64.charCodeAt(len - 1) === EQ_CHAR)
bufferLength--;
if (base64.charCodeAt(len - 2) === EQ_CHAR)
bufferLength--;
const bytes = new Uint8Array(bufferLength);
for (let i = 0, p = 0; i < len; i += 4) {
const encoded1 = lookup[base64.charCodeAt(i + 0)];
const encoded2 = lookup[base64.charCodeAt(i + 1)];
const encoded3 = lookup[base64.charCodeAt(i + 2)];
const encoded4 = lookup[base64.charCodeAt(i + 3)];
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
}
return bytes;
}
// WARNING: This file was autogenerated by wasmwrap and should not be edited manually.
const base64 = "AGFzbQEAAAABKghgAABgAn9/AGADf39/AX9gAX8AYAR/f39/AGAAAX9gAX8Bf2ACf38BfwINAQNlbnYFYWJvcnQABAMMCwcGAwAAAQIFAQIABQMBAAEGFgR/AUEAC38BQQALfwBBAwt/AEHgDAsHbgkGbWVtb3J5AgAHX19hbGxvYwABCF9fcmV0YWluAAIJX19yZWxlYXNlAAMJX19jb2xsZWN0AAQHX19yZXNldAAFC19fcnR0aV9iYXNlAwMNVWludDhBcnJheV9JRAMCDHNvbHZlQmxha2UyYgAKCAELCvQSC5IBAQV/IABB8P///wNLBEAACyMBQRBqIgQgAEEPakFwcSICQRAgAkEQSxsiBmoiAj8AIgVBEHQiA0sEQCAFIAIgA2tB//8DakGAgHxxQRB2IgMgBSADShtAAEEASARAIANAAEEASARAAAsLCyACJAEgBEEQayICIAY2AgAgAkEBNgIEIAIgATYCCCACIAA2AgwgBAsEACAACwMAAQsDAAELBgAjACQBC7sCAQF/AkAgAUUNACAAQQA6AAAgACABakEEayICQQA6AAMgAUECTQ0AIABBADoAASAAQQA6AAIgAkEAOgACIAJBADoAASABQQZNDQAgAEEAOgADIAJBADoAACABQQhNDQAgAEEAIABrQQNxIgJqIgBBADYCACAAIAEgAmtBfHEiAmpBHGsiAUEANgIYIAJBCE0NACAAQQA2AgQgAEEANgIIIAFBADYCECABQQA2AhQgAkEYTQ0AIABBADYCDCAAQQA2AhAgAEEANgIUIABBADYCGCABQQA2AgAgAUEANgIEIAFBADYCCCABQQA2AgwgACAAQQRxQRhqIgFqIQAgAiABayEBA0AgAUEgTwRAIABCADcDACAAQgA3AwggAEIANwMQIABCADcDGCABQSBrIQEgAEEgaiEADAELCwsLcgACfyAARQRAQQxBAhABIQALIAALQQA2AgAgAEEANgIEIABBADYCCCABQfD///8DIAJ2SwRAQcAKQfAKQRJBORAAAAsgASACdCIBQQAQASICIAEQBiAAKAIAGiAAIAI2AgAgACACNgIEIAAgATYCCCAAC88BAQJ/QaABQQAQASIAQQxBAxABQYABQQAQBzYCACAAQQxBBBABQQhBAxAHNgIEIABCADcDCCAAQQA2AhAgAEIANwMYIABC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";
// This is a hand-pruned version of the assemblyscript loader, removing
// a lot of functionality we don't need, saving in bundle size.
function addUtilityExports(instance) {
const extendedExports = {};
const exports = instance.exports;
const memory = exports.memory;
const alloc = exports["__alloc"];
const retain = exports["__retain"];
const rttiBase = exports["__rtti_base"] || ~0; // oob if not present
/** Gets the runtime type info for the given id. */
function getInfo(id) {
const U32 = new Uint32Array(memory.buffer);
// const count = U32[rttiBase >>> 2];
// if ((id >>>= 0) >= count) throw Error("invalid id: " + id);
return U32[((rttiBase + 4) >>> 2) + id * 2];
}
/** Allocates a new array in the module's memory and returns its retained pointer. */
extendedExports.__allocArray = (id, values) => {
const info = getInfo(id);
const align = 31 - Math.clz32((info >>> 6) & 31);
const length = values.length;
const buf = alloc(length << align, 0);
const arr = alloc(12, id);
const U32 = new Uint32Array(memory.buffer);
U32[(arr + 0) >>> 2] = retain(buf);
U32[(arr + 4) >>> 2] = buf;
U32[(arr + 8) >>> 2] = length << align;
const buffer = memory.buffer;
const view = new Uint8Array(buffer);
if (info & (1 << 14)) {
for (let i = 0; i < length; ++i)
view[(buf >>> align) + i] = retain(values[i]);
}
else {
view.set(values, buf >>> align);
}
return arr;
};
extendedExports.__getUint8Array = (ptr) => {
const U32 = new Uint32Array(memory.buffer);
const bufPtr = U32[(ptr + 4) >>> 2];
return new Uint8Array(memory.buffer, bufPtr, U32[(bufPtr - 4) >>> 2] >>> 0);
};
return demangle(exports, extendedExports);
}
/** Demangles an AssemblyScript module's exports to a friendly object structure. */
function demangle(exports, extendedExports = {}) {
// extendedExports = Object.create(extendedExports);
const setArgumentsLength = exports["__argumentsLength"]
? (length) => {
exports["__argumentsLength"].value = length;
}
: exports["__setArgumentsLength"] ||
exports["__setargc"] ||
(() => {
return {};
});
for (const internalName in exports) {
if (!Object.prototype.hasOwnProperty.call(exports, internalName))
continue;
const elem = exports[internalName];
// Only necessary if nested exports are present
// let parts = internalName.split(".");
// let curr = extendedExports;
// while (parts.length > 1) {
// let part = parts.shift();
// if (!Object.prototype.hasOwnProperty.call(curr, part as any)) curr[part as any] = {};
// curr = curr[part as any];
// }
const name = internalName.split(".")[0];
if (typeof elem === "function" && elem !== setArgumentsLength) {
(extendedExports[name] = (...args) => {
setArgumentsLength(args.length);
return elem(...args);
}).original = elem;
}
else {
extendedExports[name] = elem;
}
}
return extendedExports;
}
async function instantiateWasmSolver(module) {
const imports = {
env: {
abort() {
throw Error("Wasm aborted");
},
},
};
const result = await WebAssembly.instantiate(module, imports);
const exports = addUtilityExports(result);
return { exports };
}
async function getWasmSolver(module) {
const w = await instantiateWasmSolver(module);
const arrPtr = w.exports.__retain(w.exports.__allocArray(w.exports.Uint8Array_ID, new Uint8Array(128)));
let solution = w.exports.__getUint8Array(arrPtr);
return (puzzleBuffer, threshold, n = 4294967295) => {
solution.set(puzzleBuffer);
const hashPtr = w.exports.solveBlake2b(arrPtr, threshold, n);
solution = w.exports.__getUint8Array(arrPtr);
const hash = w.exports.__getUint8Array(hashPtr);
w.exports.__release(hashPtr);
return [solution, hash];
};
}
// Blake2B made assemblyscript compatible, adapted from (CC0 licensed):
// Blake2B in pure Javascript
// Adapted from the reference implementation in RFC7693
// Ported to Javascript by DC - https://github.com/dcposch
class Context {
constructor(outlen) {
this.b = new Uint8Array(128);
this.h = new Uint32Array(16);
this.t = 0; // input count
this.c = 0; // pointer within buffer
this.v = new Uint32Array(32);
this.m = new Uint32Array(32);
this.outlen = outlen;
}
}
// Little-endian byte access
function B2B_GET32(arr, i) {
return (arr[i] ^
(arr[i + 1] << 8) ^
(arr[i + 2] << 16) ^
(arr[i + 3] << 24));
}
// G Mixing function with everything inlined
// performance at the cost of readability, especially faster in old browsers
function B2B_G_FAST(v, m, a, b, c, d, ix, iy) {
const x0 = m[ix];
const x1 = m[ix + 1];
const y0 = m[iy];
const y1 = m[iy + 1];
// va0 are the low bits, va1 are the high bits
let va0 = v[a];
let va1 = v[a + 1];
let vb0 = v[b];
let vb1 = v[b + 1];
let vc0 = v[c];
let vc1 = v[c + 1];
let vd0 = v[d];
let vd1 = v[d + 1];
let w0, ww, xor0, xor1;
// ADD64AA(v, a, b); // v[a,a+1] += v[b,b+1] ... in JS we must store a uint64 as two uint32s
// ADD64AA(v,a,b)
w0 = va0 + vb0;
ww = ((va0 & vb0) | ((va0 | vb0) & ~w0)) >>> 31;
va0 = w0;
va1 = (va1 + vb1 + ww);
// // ADD64AC(v, a, x0, x1); // v[a, a+1] += x ... x0 is the low 32 bits of x, x1 is the high 32 bits
w0 = va0 + x0;
ww = ((va0 & x0) | ((va0 | x0) & ~w0)) >>> 31;
va0 = w0;
va1 = (va1 + x1 + ww);
// v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits
xor0 = vd0 ^ va0;
xor1 = vd1 ^ va1;
// We can just swap high and low here becaeuse its a shift by 32 bits
vd0 = xor1;
vd1 = xor0;
// ADD64AA(v, c, d);
w0 = vc0 + vd0;
ww = ((vc0 & vd0) | ((vc0 | vd0) & ~w0)) >>> 31;
vc0 = w0;
vc1 = (vc1 + vd1 + ww);
// v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits
xor0 = vb0 ^ vc0;
xor1 = vb1 ^ vc1;
vb0 = (xor0 >>> 24) ^ (xor1 << 8);
vb1 = (xor1 >>> 24) ^ (xor0 << 8);
// ADD64AA(v, a, b);
w0 = va0 + vb0;
ww = ((va0 & vb0) | ((va0 | vb0) & ~w0)) >>> 31;
va0 = w0;
va1 = (va1 + vb1 + ww);
// ADD64AC(v, a, y0, y1);
w0 = va0 + y0;
ww = ((va0 & y0) | ((va0 | y0) & ~w0)) >>> 31;
va0 = w0;
va1 = (va1 + y1 + ww);
// v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits
xor0 = vd0 ^ va0;
xor1 = vd1 ^ va1;
vd0 = (xor0 >>> 16) ^ (xor1 << 16);
vd1 = (xor1 >>> 16) ^ (xor0 << 16);
// ADD64AA(v, c, d);
w0 = vc0 + vd0;
ww = ((vc0 & vd0) | ((vc0 | vd0) & ~w0)) >>> 31;
vc0 = w0;
vc1 = (vc1 + vd1 + ww);
// v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits
xor0 = vb0 ^ vc0;
xor1 = vb1 ^ vc1;
vb0 = ((xor1 >>> 31) ^ (xor0 << 1));
vb1 = ((xor0 >>> 31) ^ (xor1 << 1));
v[a] = va0;
v[a + 1] = va1;
v[b] = vb0;
v[b + 1] = vb1;
v[c] = vc0;
v[c + 1] = vc1;
v[d] = vd0;
v[d + 1] = vd1;
}
// Initialization Vector
const BLAKE2B_IV32 = [
0xf3bcc908, 0x6a09e667, 0x84caa73b, 0xbb67ae85, 0xfe94f82b, 0x3c6ef372, 0x5f1d36f1, 0xa54ff53a,
0xade682d1, 0x510e527f, 0x2b3e6c1f, 0x9b05688c, 0xfb41bd6b, 0x1f83d9ab, 0x137e2179, 0x5be0cd19,
];
// Note these offsets have all been multiplied by two to make them offsets into
// a uint32 buffer.
const SIGMA82 = [
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 28, 20, 8, 16, 18, 30, 26, 12, 2, 24,
0, 4, 22, 14, 10, 6, 22, 16, 24, 0, 10, 4, 30, 26, 20, 28, 6, 12, 14, 2, 18, 8, 14, 18, 6, 2, 26,
24, 22, 28, 4, 12, 10, 20, 8, 0, 30, 16, 18, 0, 10, 14, 4, 8, 20, 30, 28, 2, 22, 24, 12, 16, 6,
26, 4, 24, 12, 20, 0, 22, 16, 6, 8, 26, 14, 10, 30, 28, 2, 18, 24, 10, 2, 30, 28, 26, 8, 20, 0,
14, 12, 6, 18, 4, 16, 22, 26, 22, 14, 28, 24, 2, 6, 18, 10, 0, 30, 8, 16, 12, 4, 20, 12, 30, 28,
18, 22, 6, 0, 16, 24, 4, 26, 14, 2, 8, 20, 10, 20, 4, 16, 8, 14, 12, 2, 10, 30, 22, 18, 28, 6, 24,
26, 0, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 28, 20, 8, 16, 18, 30, 26, 12,
2, 24, 0, 4, 22, 14, 10, 6,
];
// Compression function. 'last' flag indicates last block.
function blake2bCompress(ctx, last) {
const v = ctx.v;
const m = ctx.m;
// init work variables
for (let i = 0; i < 16; i++) {
v[i] = ctx.h[i];
v[i + 16] = BLAKE2B_IV32[i];
}
// low 64 bits of offset
v[24] = (v[24] ^ ctx.t);
v[25] = (v[25] ^ (ctx.t / 0x100000000));
// high 64 bits not supported, offset may not be higher than 2**53-1
// last block flag set ?
if (last) {
v[28] = ~v[28];
v[29] = ~v[29];
}
// get little-endian words
for (let i = 0; i < 32; i++) {
m[i] = B2B_GET32(ctx.b, 4 * i);
}
// twelve rounds of mixing
for (let i = 0; i < 12; i++) {
B2B_G_FAST(v, m, 0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]);
B2B_G_FAST(v, m, 2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3]);
B2B_G_FAST(v, m, 4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5]);
B2B_G_FAST(v, m, 6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7]);
B2B_G_FAST(v, m, 0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9]);
B2B_G_FAST(v, m, 2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11]);
B2B_G_FAST(v, m, 4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13]);
B2B_G_FAST(v, m, 6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15]);
}
for (let i = 0; i < 16; i++) {
ctx.h[i] = ctx.h[i] ^ v[i] ^ v[i + 16];
}
}
/**
* FRIENDLY CAPTCHA optimization only, does not reset ctx.t (global byte counter)
* Assumes no key
*/
function blake2bResetForShortMessage(ctx, input) {
// Initialize State vector h with IV
for (let i = 0; i < 16; i++) {
ctx.h[i] = BLAKE2B_IV32[i];
}
// Danger: These operations and resetting are really only possible because our input is exactly 128 bytes
ctx.b.set(input);
// ctx.m.fill(0);
// ctx.v.fill(0);
ctx.h[0] ^= 0x01010000 ^ ctx.outlen;
}
// This is not an enum to save some bytes in the output bundle.
const SOLVER_TYPE_JS = 1;
const SOLVER_TYPE_WASM = 2;
const CHALLENGE_SIZE_BYTES = 128;
const HASH_SIZE_BYTES = 32;
/**
* Solve the blake2b hashing problem, re-using the memory between different attempts (which solves up to 50% faster).
*
* This only changes the last 4 bytes of the input array to find a solution. To find multiple solutions
* one could call this function multiple times with the 4 bytes in front of those last 4 bytes varying.
*
*
* The goal is to find a nonce that, hashed together with the rest of the input header, has a value of its
* most significant 32bits that is below some threshold.
* Approximately this means: the hash value of it starts with K zeroes (little endian), which is expected to be
* increasingly difficult as K increases.
*
* In practice you should ask the client to solve multiple (easier) puzzles which should reduce variance and also allows us
* to show a progress bar.
* @param input challenge bytes
* @param threshold u32 value under which the solution's hash should be below.
*/
function solveBlake2bEfficient(input, threshold, n) {
if (input.length != CHALLENGE_SIZE_BYTES) {
throw Error("Invalid input");
}
const buf = input.buffer;
const view = new DataView(buf);
const ctx = new Context(HASH_SIZE_BYTES);
ctx.t = CHALLENGE_SIZE_BYTES;
const start = view.getUint32(124, true);
const end = start + n;
for (let i = start; i < end; i++) {
view.setUint32(124, i, true);
blake2bResetForShortMessage(ctx, input);
blake2bCompress(ctx, true);
if (ctx.h[0] < threshold) {
if (ASC_TARGET == 0) {
// JS
return new Uint8Array(ctx.h.buffer);
}
//@ts-ignore
return Uint8Array.wrap(ctx.h.buffer);
}
}
return new Uint8Array(0);
}
async function getJSSolver() {
return (puzzleBuffer, threshold, n = 4294967295) => {
const hash = solveBlake2bEfficient(puzzleBuffer, threshold, n);
return [puzzleBuffer, hash];
};
}
if (!Uint8Array.prototype.slice) {
Object.defineProperty(Uint8Array.prototype, "slice", {
value: function (begin, end) {
return new Uint8Array(Array.prototype.slice.call(this, begin, end));
},
});
}
self.ASC_TARGET = 0;
// 1 for JS, 2 for WASM
let solverType;
// Puzzle consisting of zeroes
let setSolver;
const solver = new Promise((resolve) => (setSolver = resolve));
self.onerror = (evt) => {
self.postMessage({
type: "error",
message: JSON.stringify(evt),
});
};
self.onmessage = async (evt) => {
const data = evt.data;
try {
/**
* Compile the WASM and setup the solver.
* If WASM support is not present, it uses the JS version instead.
*/
if (data.type === "solver") {
if (data.forceJS) {
solverType = SOLVER_TYPE_JS;
const s = await getJSSolver();
setSolver(s);
}
else {
try {
solverType = SOLVER_TYPE_WASM;
const module = WebAssembly.compile(decode(base64));
const s = await getWasmSolver(await module);
setSolver(s);
}
catch (e) {
console.log("FriendlyCaptcha failed to initialize WebAssembly, falling back to Javascript solver: " + e.toString());
solverType = SOLVER_TYPE_JS;
const s = await getJSSolver();
setSolver(s);
}
}
self.postMessage({
type: "ready",
solver: solverType,
});
}
else if (data.type === "start") {
const solve = await solver;
self.postMessage({
type: "started",
});
let totalH = 0;
let solution;
// We loop over a uint32 to find as solution, it is technically possible (but extremely unlikely - only possible with very high difficulty) that
// there is no solution, here we loop over one byte further up too in case that happens.
for (let b = 0; b < 256; b++) {
data.puzzleSolverInput[123] = b;
const [s, hash] = solve(data.puzzleSolverInput, data.threshold);
if (hash.length === 0) {
// This means 2^32 puzzles were evaluated, which takes a while in a browser!
// As we try 256 times, this is not fatal
console.warn("FC: Internal error or no solution found");
totalH += Math.pow(2, 32) - 1;
continue;
}
solution = s;
break;
}
const view = new DataView(solution.slice(-4).buffer);
totalH += view.getUint32(0, true);
self.postMessage({
type: "done",
solution: solution.slice(-8),
h: totalH,
puzzleIndex: data.puzzleIndex,
puzzleNumber: data.puzzleNumber,
});
}
}
catch (e) {
setTimeout(() => {
throw e;
});
}
};
})();