├── .prettierrc ├── .gitignore ├── src ├── interfaces.d.ts ├── tsconfig.json ├── blake2b │ ├── types.d.ts │ ├── blake2b64.ts │ └── blake2b.ts ├── entryWasm.ts ├── constants.ts ├── diagnostics.ts ├── api │ ├── js.ts │ └── wasm.ts ├── timestamp.ts ├── encoding.ts ├── puzzle.ts ├── solver.ts ├── solverWasm.ts ├── base64.ts └── loader.ts ├── examples ├── benchmark │ ├── interfaces.d.ts │ ├── build.sh │ ├── rollup.config.ts │ ├── tsconfig.json │ ├── index.html │ ├── babel.config.js │ └── benchmark.ts ├── README.md ├── solveTS.ts └── solveWasm.ts ├── test ├── tsconfig.json ├── setup.ts ├── util.ts ├── timestamp.test.ts ├── solver.test.ts ├── puzzle.jstest.ts ├── solver.astest.ts ├── blake2b64.astest.ts ├── blake2b.test.ts └── as-pect.d.ts ├── tsconfig.common.json ├── jest.config.js ├── tsconfig.lint.json ├── .github └── FUNDING.yml ├── tsconfig.json ├── .eslintrc.js ├── as-pect.config.js ├── package.json └── README.md /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | coverage/ 4 | build -------------------------------------------------------------------------------- /src/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | import "assemblyscript/std/portable/index"; 2 | -------------------------------------------------------------------------------- /examples/benchmark/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | import "assemblyscript/std/portable/index" 2 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "assemblyscript/std/assembly.json" 3 | } 4 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "assemblyscript/std/assembly.json" 3 | } 4 | -------------------------------------------------------------------------------- /src/blake2b/types.d.ts: -------------------------------------------------------------------------------- 1 | declare type u64 = number; 2 | declare type i64 = number; 3 | -------------------------------------------------------------------------------- /examples/benchmark/build.sh: -------------------------------------------------------------------------------- 1 | ../../node_modules/.bin/rollup -c rollup.config.ts 2 | ../../node_modules/.bin/babel build/benchmark.js -o build/benchmark.compat.js 3 | -------------------------------------------------------------------------------- /src/entryWasm.ts: -------------------------------------------------------------------------------- 1 | export { solveBlake2bEfficient as solveBlake2b } from "./solverWasm"; 2 | /* eslint-disable-next-line */ 3 | export const Uint8Array_ID = idof(); 4 | -------------------------------------------------------------------------------- /tsconfig.common.json: -------------------------------------------------------------------------------- 1 | { // CommonJS (for node) 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/node", 5 | "module": "commonjs", 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | // This is not an enum to save some bytes in the output bundle. 2 | export const SOLVER_TYPE_JS = 1; 3 | export const SOLVER_TYPE_WASM = 2; 4 | 5 | export const CHALLENGE_SIZE_BYTES = 128; 6 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | To execute these examples with ts-node you will first need to set the module type to `commonjs` in `tsconfig.json`, then you can run: 4 | 5 | ``` 6 | ts-node --transpile-only examples/solveWasm.ts 7 | ``` 8 | -------------------------------------------------------------------------------- /test/setup.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | const globalScope = 3 | (typeof window !== "undefined" && window) || (typeof global !== "undefined" && global) || self; 4 | Object.assign(globalScope, { 5 | ASC_TARGET: 0, 6 | // unchecked: (v: any) => v, 7 | }); 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | setupFiles: ["/test/setup.ts"], 5 | testMatch: [ "**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|(js)?test).[jt]s?(x)"], 6 | testPathIgnorePatterns: ["dist/"] 7 | }; -------------------------------------------------------------------------------- /src/diagnostics.ts: -------------------------------------------------------------------------------- 1 | export function createDiagnosticsBuffer(solverID: 1 | 2, timeToSolved: number) { 2 | const arr = new Uint8Array(3); 3 | const view = new DataView(arr.buffer); 4 | view.setUint8(0, solverID); 5 | view.setUint16(1, timeToSolved); 6 | 7 | return arr; 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "./node_modules/assemblyscript/std/assembly/**/*.ts", 5 | "./src/**/*.ts", 6 | "./test/**/*.ts", 7 | ], 8 | "exclude": [ 9 | "node_modules/", 10 | "dist/", 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/api/js.ts: -------------------------------------------------------------------------------- 1 | import { solveBlake2bEfficient } from "../solver"; 2 | 3 | export async function getJSSolver() { 4 | return (puzzleBuffer: Uint8Array, threshold: number, n = 4294967295) => { 5 | const hash = solveBlake2bEfficient(puzzleBuffer, threshold, n); 6 | return [puzzleBuffer, hash]; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /examples/benchmark/rollup.config.ts: -------------------------------------------------------------------------------- 1 | import typescript from "rollup-plugin-typescript2"; 2 | 3 | export default { 4 | input: `benchmark.ts`, 5 | output: [{ file: "build/benchmark.js", format: "iife" }], 6 | plugins: [ 7 | typescript({ 8 | include: ["./**/*.ts", "../../src/**/*.ts"], 9 | }), 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /test/util.ts: -------------------------------------------------------------------------------- 1 | export function uint8ArrayWithValues(values: Array): Uint8Array { 2 | const arr = new Uint8Array(values.length); 3 | arr.set(values); 4 | return arr; 5 | } 6 | 7 | export function uint32ArrayWithValues(values: Array): Uint32Array { 8 | const arr = new Uint32Array(values.length); 9 | arr.set(values); 10 | return arr; 11 | } 12 | -------------------------------------------------------------------------------- /src/timestamp.ts: -------------------------------------------------------------------------------- 1 | function toU32(value: number): u32 { 2 | return (value as u32) >>> 0; 3 | } 4 | 5 | /** 6 | * Unix timestamp in seconds since epoch as unsigned 32 bit integer right now. 7 | */ 8 | export function getTimestampInSeconds(): u32 { 9 | return toU32((Date.now() / 1000) as f64); 10 | } 11 | 12 | export function dateToTimestampInSeconds(date: Date): u32 { 13 | return toU32((date.getTime() / 1000) as f64); 14 | } 15 | 16 | export function timestampInSecondsToDate(timestamp: u32): Date { 17 | return new Date((timestamp as u64) * 1000); 18 | } 19 | -------------------------------------------------------------------------------- /examples/solveTS.ts: -------------------------------------------------------------------------------- 1 | import { solveBlake2bEfficient } from "../src/solver"; 2 | import { difficultyToThreshold } from "../src/encoding"; 3 | 4 | const threshold = difficultyToThreshold(175); 5 | 6 | console.log("Threshold:", threshold); 7 | 8 | //@ts-ignore 9 | const globalScope = 10 | (typeof window !== "undefined" && window) || (typeof global !== "undefined" && global) || self; 11 | Object.assign(globalScope, { ASC_TARGET: 0 }); 12 | 13 | const input = new Uint8Array(128); 14 | input.set([1, 2, 3]); 15 | console.time("solve-time"); 16 | const h = solveBlake2bEfficient(input, threshold, 10000000); 17 | console.timeEnd("solve-time"); 18 | 19 | console.log(input.slice(-8)); 20 | console.log(h); 21 | -------------------------------------------------------------------------------- /examples/benchmark/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build", 4 | "rootDir": "../../", 5 | "module": "ESNext", 6 | "target": "ES2017", 7 | "lib": ["DOM", "ESNext"], 8 | "strict": true, 9 | "sourceMap": true, 10 | "experimentalDecorators": true, 11 | }, 12 | "include": [ 13 | "*.ts", 14 | "../../src/**/*.ts" 15 | ], 16 | // "exclude": [ 17 | // "node_modules/", 18 | // "dist/", 19 | // "./src/blake2b/blake2b64.ts", 20 | // "./src/entryWasm.ts", 21 | // "./src/solverWasm.ts", 22 | // "./test/**/*.astest.ts", 23 | // "./test/as-pect.d.ts", 24 | // ] 25 | } 26 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: FriendlyCaptcha 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /examples/benchmark/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | friendly-pow JS fallback benchmark 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

friendly-pow benchmark

17 | 18 | 19 | -------------------------------------------------------------------------------- /src/api/wasm.ts: -------------------------------------------------------------------------------- 1 | import { instantiateWasmSolver } from "../loader"; 2 | 3 | export async function getWasmSolver(module: any) { 4 | const w = await instantiateWasmSolver(module); 5 | 6 | const arrPtr = w.exports.__retain( 7 | w.exports.__allocArray(w.exports.Uint8Array_ID, new Uint8Array(128)) 8 | ); 9 | let solution = w.exports.__getUint8Array(arrPtr); 10 | 11 | return (puzzleBuffer: Uint8Array, threshold: number, n = 4294967295) => { 12 | (solution as Uint8Array).set(puzzleBuffer); 13 | const hashPtr = w.exports.solveBlake2b(arrPtr, threshold, n); 14 | solution = w.exports.__getUint8Array(arrPtr); 15 | const hash = w.exports.__getUint8Array(hashPtr); 16 | w.exports.__release(hashPtr); 17 | return [solution, hash]; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /examples/benchmark/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "exclude": [ 7 | "transform-regenerator", "transform-async-to-generator" 8 | ], 9 | "targets": { 10 | "browsers": [">0.05%", "not dead", "not ie 11", "not ie_mob 11", "firefox 35"] 11 | }, 12 | "modules": "auto", 13 | "useBuiltIns": "entry", 14 | "corejs": 3, 15 | } 16 | ] 17 | ], 18 | plugins: [ 19 | ["module:fast-async", { 20 | "compiler": { 21 | "promises": true, 22 | "generators": true 23 | }, 24 | "useRuntimeModule": false 25 | }], 26 | ], 27 | // ignore: [/node_modules\/(?!friendly-pow)/] 28 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "rootDir": "./src", 5 | "module": "ESNext", 6 | "target": "ES2019", 7 | "lib": ["DOM", "ESNext"], 8 | "strict": true, 9 | "moduleResolution": "node", 10 | "inlineSourceMap": true, 11 | "inlineSources": true, 12 | "esModuleInterop": true, 13 | "experimentalDecorators": true, 14 | "declaration": true, 15 | }, 16 | "include": [ 17 | "./node_modules/assemblyscript/std/assembly/**/*.ts", 18 | "./src/**/*", 19 | ], 20 | "exclude": [ 21 | "node_modules/", 22 | "dist/", 23 | "./src/blake2b/blake2b64.ts", 24 | "./src/entryWasm.ts", 25 | "./src/solverWasm.ts", 26 | "./test/**/*.astest.ts", 27 | "./test/as-pect.d.ts", 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /src/encoding.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Maps a value between 0 and 255 to a difficulty threshold (as uint32) 3 | * Difficulty 0 maps to 99.99% probability of being right on the first attempt 4 | * Anything above 250 needs 2^32 tries on average to solve. 5 | * 150 to 180 seems reasonable 6 | */ 7 | export function difficultyToThreshold(value: u8): u32 { 8 | if (value > 255) { 9 | value = 255; 10 | } else if (value < 0) { 11 | value = 0; 12 | } 13 | 14 | return (Math.pow(2, (255.999 - (value as f64)) / 8.0) as u32) >>> 0; 15 | } 16 | 17 | /** 18 | * Maps a value between 0 and 255 to a time duration in seconds that a puzzle is valid for. 19 | */ 20 | export function expiryToDurationInSeconds(value: u8): u32 { 21 | if (value > 255) { 22 | value = 255; 23 | } else if (value < 0) { 24 | value = 0; 25 | } 26 | 27 | return (value as u32) * 300; 28 | } 29 | -------------------------------------------------------------------------------- /test/timestamp.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getTimestampInSeconds, 3 | dateToTimestampInSeconds, 4 | timestampInSecondsToDate, 5 | } from "../src/timestamp"; 6 | 7 | describe("timestamp utils", () => { 8 | test("unix time in seconds as u32", () => { 9 | const t = 1591632913 as u32; 10 | expect(getTimestampInSeconds()).toBeGreaterThan(t); // Time of writing this test 11 | expect(getTimestampInSeconds()).toBeLessThan(t + 1000000000); // Many years (~30) into the future 12 | }); 13 | 14 | test("unix timestamp to date conversion stable", () => { 15 | const ts = dateToTimestampInSeconds(new Date(1592879769186)); 16 | const d = timestampInSecondsToDate(ts); 17 | const tsRevert = dateToTimestampInSeconds(d); 18 | const dRevert = timestampInSecondsToDate(tsRevert); 19 | 20 | expect(dRevert.getTime()).toBe(d.getTime()); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/solver.test.ts: -------------------------------------------------------------------------------- 1 | import { solveBlake2bEfficient } from "../src/solver"; 2 | import { difficultyToThreshold } from "../src/encoding"; 3 | 4 | describe("solver", () => { 5 | test("Can solve simple challenge within 50,000 tries", () => { 6 | const threshold = difficultyToThreshold(100); 7 | const input = new Uint8Array(128); 8 | const hash = solveBlake2bEfficient(input, threshold, 50_000); 9 | expect(hash.length).toBeGreaterThan(0); 10 | }); 11 | 12 | test("JS Solver is somewhat fast", () => { 13 | const threshold = difficultyToThreshold(255); // Basically impossible 14 | const input = new Uint8Array(128); 15 | const t = Date.now(); 16 | const n = 150_000; 17 | const hash = solveBlake2bEfficient(input, threshold, n); 18 | const hashRate: f64 = (n as f64) / (((Date.now() - t) as f64) / 1000.0); 19 | 20 | expect(hash.length).toBe(0); // No solution found 21 | expect(hashRate).toBeGreaterThan(50_000); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/puzzle.jstest.ts: -------------------------------------------------------------------------------- 1 | import { solveBlake2bEfficient } from "../src/solver"; 2 | import { difficultyToThreshold } from "../src/encoding"; 3 | 4 | describe("puzzle generation and verification", () => { 5 | const difficulty = 50; 6 | 7 | const puzzleFixture = new Uint8Array(37); 8 | // prettier-ignore 9 | puzzleFixture.set([ 10 | 97, 131, 208, 51, 0, 0, 0, 0, 0, 0, 11 | 0, 0, 1, 10, 1, 50, 0, 0, 0, 0, 12 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13 | 0, 0, 1, 2, 3, 4, 5 14 | ]) // A very easy puzzle with difficulty 50 15 | const expectedSolution = new Uint8Array(8); 16 | expectedSolution.set([0, 0, 0, 0, 154, 0, 0, 0]); 17 | 18 | const input = new Uint8Array(128); 19 | input.set(puzzleFixture.slice(), 0); 20 | solveBlake2bEfficient(input, difficultyToThreshold(difficulty), Math.pow(2, 32)); 21 | const foundSolution = input.slice(120); 22 | 23 | test("Finds the expected solution", () => { 24 | expect(foundSolution).toStrictEqual(expectedSolution); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /examples/benchmark/benchmark.ts: -------------------------------------------------------------------------------- 1 | import { getJSSolver } from "../../src/api/js"; 2 | 3 | function runBenchmark() { 4 | getJSSolver().then(async (solver) => { 5 | const buf = new Uint8Array(128); 6 | buf.set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 255, 230, 210, 255, 255, 255, 255]); // At least add a few non-zero values in buffer 7 | 8 | const start = new Date().getTime(); 9 | console.time("total benchmark time"); 10 | 11 | const N = 25_000; 12 | let i = 0; 13 | 14 | const f = () => { 15 | const startStep = new Date().getTime(); 16 | solver(new Uint8Array(128), 0, N); 17 | const timeElapsed = (new Date().getTime() - startStep) * 0.001; 18 | document.body.innerHTML += `

Hashrate ${N / timeElapsed / 1000} KH/s

`; 19 | 20 | i++; 21 | if (i < 20) { 22 | setTimeout(() => f()); 23 | } else { 24 | console.timeEnd("total benchmark time"); 25 | console.log("Done"); 26 | } 27 | }; 28 | 29 | f(); 30 | }); 31 | } 32 | 33 | runBenchmark(); 34 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "project": "tsconfig.lint.json", 10 | "sourceType": "module" 11 | }, 12 | "plugins": [ 13 | "@typescript-eslint" 14 | ], 15 | "rules": { 16 | "@typescript-eslint/member-delimiter-style": [ 17 | "error", 18 | { 19 | "multiline": { 20 | "delimiter": "semi", 21 | "requireLast": true 22 | }, 23 | "singleline": { 24 | "delimiter": "semi", 25 | "requireLast": false 26 | } 27 | } 28 | ], 29 | "@typescript-eslint/semi": [ 30 | "error", 31 | "always" 32 | ], 33 | "@typescript-eslint/no-use-before-define": "off", 34 | "@typescript-eslint/explicit-function-return-type": "off", 35 | "@typescript-eslint/no-explicit-any": "off", 36 | "@typescript-eslint/no-unused-vars": [ 37 | "warn", 38 | { 39 | "argsIgnorePattern": "^_", 40 | "varsIgnorePattern": "^_" 41 | } 42 | ], 43 | "@typescript-eslint/ban-ts-ignore": "off", 44 | }, 45 | "extends": [ 46 | "eslint:recommended", 47 | "plugin:@typescript-eslint/eslint-recommended", 48 | "plugin:@typescript-eslint/recommended" 49 | ] 50 | }; -------------------------------------------------------------------------------- /src/puzzle.ts: -------------------------------------------------------------------------------- 1 | import { expiryToDurationInSeconds } from "./encoding"; 2 | import { CHALLENGE_SIZE_BYTES } from "./constants"; 3 | 4 | export const PUZZLE_TIMESTAMP_OFFSET = 0; 5 | export const ACCOUNT_ID_OFFSET = 4; 6 | export const APP_ID_OFFSET = 8; 7 | export const PUZZLE_VERSION_OFFSET = 12; 8 | export const PUZZLE_EXPIRY_OFFSET = 13; 9 | export const NUMBER_OF_PUZZLES_OFFSET = 14; 10 | export const PUZZLE_DIFFICULTY_OFFSET = 15; 11 | export const PUZZLE_NONCE_OFFSET = 24; 12 | export const PUZZLE_USER_DATA_OFFSET = 32; 13 | 14 | export const PUZZLE_USER_DATA_MAX_LENGTH = 32; 15 | 16 | export function getPuzzleSolverInputs(puzzleBuffer: Uint8Array, numPuzzles: number): Uint8Array[] { 17 | const startingPoints: Uint8Array[] = []; 18 | 19 | for (let i = 0; i < numPuzzles; i++) { 20 | const input = new Uint8Array(CHALLENGE_SIZE_BYTES); 21 | input.set(puzzleBuffer); 22 | input[120] = i; 23 | startingPoints.push(input); 24 | } 25 | return startingPoints; 26 | } 27 | 28 | /** 29 | * Combine multiple solutions (8 byte values) into a single array 30 | * @param solutions 31 | */ 32 | export function combineSolutions(solutions: Uint8Array[]): Uint8Array { 33 | const combined = new Uint8Array(solutions.length * 8); 34 | for (let i = 0; i < solutions.length; i++) { 35 | combined.set(solutions[i], i * 8); 36 | } 37 | return combined; 38 | } 39 | 40 | /** 41 | * Time in seconds the puzzle is valid for. 42 | * @param puzzleBuffer 43 | */ 44 | export function getPuzzleTTL(puzzleBuffer: Uint8Array) { 45 | return expiryToDurationInSeconds(puzzleBuffer[PUZZLE_EXPIRY_OFFSET]); 46 | } 47 | -------------------------------------------------------------------------------- /as-pect.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** Globs that represent the test entry points. */ 3 | include: ["test/**/*.test.ts", "test/**/*.astest.ts"], 4 | /** Modules that should be added to the compilation */ 5 | add: ["test/**/*.include.ts"], 6 | /** Compiler flags for each module. */ 7 | flags: { 8 | /** To output a wat file, uncomment the following line. */ 9 | // "--textFile": ["output.wat"], 10 | "-O3": [], 11 | /** A runtime must be provided here. */ 12 | "--runtime": ["stub"], // Acceptable values are: full, half, stub (arena), and none 13 | }, 14 | /** Disclude tests that match this regex. */ 15 | disclude: [/node_modules/], 16 | /** Add your required AssemblyScript imports here in this function. */ 17 | imports(memory, createImports, instantiateSync, binary) { 18 | let result; // Imports can reference this 19 | const myImports = { 20 | // put your web assembly imports here, and return the module 21 | }; 22 | result = instantiateSync(binary, createImports(myImports)); 23 | // return the entire result object from the loader 24 | return result; 25 | }, 26 | // uncomment the following section if you require wasi support 27 | /* 28 | wasi: { 29 | // pass args here 30 | args: [], 31 | // inherit from env 32 | env: process.env, 33 | preopens: { 34 | // put your preopen's here 35 | }, 36 | // let as-pect finish what it needs to finish 37 | returnOnExit: false, 38 | }, 39 | */ 40 | /** 41 | * To create your own custom reporter, please check out the Core API. 42 | */ 43 | // reporter: new CustomReporter(), 44 | /** Output the binary wasm file: [testname].spec.wasm */ 45 | outputBinary: false, 46 | }; 47 | -------------------------------------------------------------------------------- /examples/solveWasm.ts: -------------------------------------------------------------------------------- 1 | import { difficultyToThreshold } from "../src/encoding"; 2 | import { solveBlake2bEfficient } from "../src/solver"; 3 | import { instantiateWasmSolver } from "../src/loader"; 4 | import { base64 } from "../dist/wasm/optimized.wrap"; 5 | import { decode } from "../src/base64"; 6 | 7 | //@ts-ignore 8 | const globalScope = 9 | (typeof window !== "undefined" && window) || (typeof global !== "undefined" && global) || self; 10 | Object.assign(globalScope, { ASC_TARGET: 0 }); 11 | 12 | const threshold = difficultyToThreshold(175); 13 | let myModule: any; 14 | const imports = { 15 | console: { 16 | log: (msgPtr: number) => { 17 | console.log(myModule.exports.__getString(msgPtr)); 18 | }, 19 | }, 20 | }; 21 | 22 | console.log("Threshold:", threshold); 23 | 24 | const wasmBytes = decode(base64); 25 | const wasmModule = WebAssembly.compile(wasmBytes); 26 | 27 | wasmModule.then((module: any) => 28 | instantiateWasmSolver(module).then((w) => { 29 | myModule = w; 30 | 31 | const arrPtr = w.exports.__retain( 32 | w.exports.__allocArray(w.exports.Uint8Array_ID, new Uint8Array(128)) 33 | ); 34 | let solution = w.exports.__getUint8Array(arrPtr); 35 | solution.set([1, 2, 3]); 36 | 37 | console.time("solve-time"); 38 | const hashPtr = w.exports.solveBlake2b(arrPtr, threshold, 100000000); 39 | console.timeEnd("solve-time"); 40 | const wasmHash = w.exports.__getUint8Array(hashPtr); 41 | w.exports.__release(hashPtr); 42 | const jsHash = solveBlake2bEfficient(solution, threshold, 1); // Only 1 attempt required as we start from the solution. 43 | 44 | console.log("Solution WASM:", solution.slice(-8)); 45 | console.log("Solution JS", solution.slice(-8)); 46 | console.log( 47 | "hash equal:", 48 | jsHash.every((val, index) => val === wasmHash[index]) 49 | ); 50 | w.exports.__release(arrPtr); 51 | }) 52 | ); 53 | -------------------------------------------------------------------------------- /test/solver.astest.ts: -------------------------------------------------------------------------------- 1 | import { solveBlake2bEfficient } from "../src/solverWasm"; 2 | import { difficultyToThreshold } from "../src/encoding"; 3 | 4 | const puzzleFixture = new Uint8Array(37); 5 | // prettier-ignore 6 | puzzleFixture.set([ 7 | 97, 131, 208, 51, 0, 0, 0, 0, 0, 0, 8 | 0, 0, 1, 10, 1, 50, 0, 0, 0, 0, 9 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10 | 0, 0, 1, 2, 3, 4, 5 11 | ]) // A very easy puzzle with difficulty 50 12 | const expectedSolution = new Uint8Array(8); 13 | expectedSolution.set([0, 0, 0, 0, 154, 0, 0, 0]); 14 | 15 | // const expectedSolution = new Uint8Array([]); 16 | 17 | describe("solver", () => { 18 | test("Can solve over and over", () => { 19 | const threshold = difficultyToThreshold(50); 20 | const input = new Uint8Array(128); 21 | 22 | for (let i = 0; i < 1_000; i++) { 23 | const hash = solveBlake2bEfficient(input, threshold, 100_000); 24 | expect(hash.length).toBeGreaterThan(0); 25 | } 26 | }); 27 | 28 | test("WASM solver also finds expected solution", () => { 29 | const threshold = difficultyToThreshold(50); 30 | const input = new Uint8Array(128); 31 | input.set(puzzleFixture); 32 | 33 | const hash = solveBlake2bEfficient(input, threshold, 100_000); 34 | expect(hash.length).toBeGreaterThan(0); 35 | const foundSolution = input.slice(120); 36 | expect(foundSolution).toStrictEqual(expectedSolution); 37 | }); 38 | 39 | test("WASM Solver is very fast", () => { 40 | const threshold = difficultyToThreshold(255); // Basically impossible 41 | const input = new Uint8Array(128); 42 | const t = Date.now(); 43 | const n = 1_000_000; 44 | const hash = solveBlake2bEfficient(input, threshold, n); 45 | const hashRate: f64 = (n as f64) / (((Date.now() - t) as f64) / 1000.0); 46 | 47 | expect(hash.length).toBe(0); // No solution found 48 | expect(hashRate).toBeGreaterThan(1_250_000); // I hit 2.3M/s on Ryzen 3900X 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/solver.ts: -------------------------------------------------------------------------------- 1 | import { blake2bResetForShortMessage, blake2bCompress, Context } from "./blake2b/blake2b"; 2 | import { CHALLENGE_SIZE_BYTES } from "./constants"; 3 | 4 | export const HASH_SIZE_BYTES = 32; 5 | 6 | /** 7 | * Solve the blake2b hashing problem, re-using the memory between different attempts (which solves up to 50% faster). 8 | * 9 | * This only changes the last 4 bytes of the input array to find a solution. To find multiple solutions 10 | * one could call this function multiple times with the 4 bytes in front of those last 4 bytes varying. 11 | * 12 | * 13 | * The goal is to find a nonce that, hashed together with the rest of the input header, has a value of its 14 | * most significant 32bits that is below some threshold. 15 | * Approximately this means: the hash value of it starts with K zeroes (little endian), which is expected to be 16 | * increasingly difficult as K increases. 17 | * 18 | * In practice you should ask the client to solve multiple (easier) puzzles which should reduce variance and also allows us 19 | * to show a progress bar. 20 | * @param input challenge bytes 21 | * @param threshold u32 value under which the solution's hash should be below. 22 | */ 23 | export function solveBlake2bEfficient(input: Uint8Array, threshold: u32, n: u32): Uint8Array { 24 | if (input.length != CHALLENGE_SIZE_BYTES) { 25 | throw Error("Invalid input"); 26 | } 27 | 28 | const buf = input.buffer; 29 | const view = new DataView(buf); 30 | 31 | const ctx = new Context(HASH_SIZE_BYTES); 32 | ctx.t = CHALLENGE_SIZE_BYTES; 33 | 34 | const start = view.getUint32(124, true); 35 | const end = start + n; 36 | 37 | for (let i: u32 = start; i < end; i++) { 38 | view.setUint32(124, i, true); 39 | 40 | blake2bResetForShortMessage(ctx, input); 41 | blake2bCompress(ctx, true); 42 | 43 | if (ctx.h[0] < threshold) { 44 | if (ASC_TARGET == 0) { 45 | // JS 46 | return new Uint8Array(ctx.h.buffer); 47 | } 48 | //@ts-ignore 49 | return Uint8Array.wrap(ctx.h.buffer); 50 | } 51 | } 52 | 53 | return new Uint8Array(0); 54 | } 55 | -------------------------------------------------------------------------------- /src/solverWasm.ts: -------------------------------------------------------------------------------- 1 | import { blake2bResetForShortMessage, blake2bCompress, Context } from "./blake2b/blake2b64"; 2 | 3 | const CHALLENGE_SIZE_BYTES = 128; 4 | const HASH_SIZE_BYTES = 32; 5 | 6 | // Uncomment for debugging 7 | // //@ts-ignore 8 | // @external("console", "log") 9 | // declare function log(msg: string): void; 10 | 11 | /** 12 | * Solve the blake2b hashing problem, re-using the memory between different attempts (which solves up to 50% faster). 13 | * 14 | * This only changes the last 4 bytes of the input array to find a solution. To find multiple solutions 15 | * one could call this function multiple times with the 4 bytes in front of those last 4 bytes varying. 16 | * 17 | * 18 | * The goal is to find a nonce that, hashed together with the rest of the input header, has a value of its 19 | * most significant 32bits that is below some threshold. 20 | * Approximately this means: the hash value of it starts with K zeroes (little endian), which is expected to be 21 | * increasingly difficult as K increases. 22 | * 23 | * In practice you should ask the client to solve multiple (easier) puzzles which should reduce variance and also allows us 24 | * to show a progress bar. 25 | * @param input challenge bytes 26 | * @param threshold u32 value under which the solution's hash should be below. 27 | */ 28 | export function solveBlake2bEfficient(input: Uint8Array, threshold: u32, n: u32): Uint8Array { 29 | if (input.length != CHALLENGE_SIZE_BYTES) { 30 | throw new Error("Invalid input"); 31 | } 32 | 33 | const arrayPtr = changetype(input); 34 | const bufferPtr = load(arrayPtr); 35 | 36 | const ctx = new Context(HASH_SIZE_BYTES); 37 | const h = ctx.h; 38 | ctx.t = CHALLENGE_SIZE_BYTES; 39 | 40 | const start = load(bufferPtr + 124); 41 | const end = start + n; 42 | for (let i: u32 = start; i < end; i++) { 43 | store(bufferPtr + 124, i); 44 | // log(input.slice(124)); 45 | 46 | blake2bResetForShortMessage(ctx); 47 | blake2bCompress(ctx, true, bufferPtr); 48 | 49 | if ((unchecked(h[0]) as u32) < threshold) { 50 | return Uint8Array.wrap(h.buffer); 51 | } 52 | } 53 | // No solution found signal. 54 | return new Uint8Array(0); 55 | } 56 | -------------------------------------------------------------------------------- /src/base64.ts: -------------------------------------------------------------------------------- 1 | // Adapted from the base64-arraybuffer package implementation 2 | // (https://github.com/niklasvh/base64-arraybuffer, MIT licensed) 3 | 4 | const CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 5 | const EQ_CHAR = "=".charCodeAt(0); 6 | 7 | // Use a lookup table to find the index. 8 | const lookup = new Uint8Array(256); 9 | for (let i = 0; i < CHARS.length; i++) { 10 | lookup[CHARS.charCodeAt(i)] = i; 11 | } 12 | 13 | export function encode(bytes: Uint8Array): string { 14 | const len = bytes.length; 15 | let base64 = ""; 16 | 17 | for (let i = 0; i < len; i += 3) { 18 | const b0 = bytes[i + 0]; 19 | const b1 = bytes[i + 1]; 20 | const b2 = bytes[i + 2]; 21 | // This temporary variable stops the NextJS 13 compiler from breaking this code in optimization. 22 | // See issue https://github.com/FriendlyCaptcha/friendly-challenge/issues/165 23 | let t = ""; 24 | t += CHARS.charAt(b0 >>> 2); 25 | t += CHARS.charAt(((b0 & 3) << 4) | (b1 >>> 4)); 26 | t += CHARS.charAt(((b1 & 15) << 2) | (b2 >>> 6)); 27 | t += CHARS.charAt(b2 & 63); 28 | base64 += t; 29 | } 30 | 31 | if (len % 3 === 2) { 32 | base64 = base64.substring(0, base64.length - 1) + "="; 33 | } else if (len % 3 === 1) { 34 | base64 = base64.substring(0, base64.length - 2) + "=="; 35 | } 36 | 37 | return base64; 38 | } 39 | 40 | export function decode(base64: string): Uint8Array { 41 | const len = base64.length; 42 | let bufferLength = (len * 3) >>> 2; // * 0.75 43 | 44 | if (base64.charCodeAt(len - 1) === EQ_CHAR) bufferLength--; 45 | if (base64.charCodeAt(len - 2) === EQ_CHAR) bufferLength--; 46 | 47 | const bytes = new Uint8Array(bufferLength); 48 | for (let i = 0, p = 0; i < len; i += 4) { 49 | const encoded1 = lookup[base64.charCodeAt(i + 0)]; 50 | const encoded2 = lookup[base64.charCodeAt(i + 1)]; 51 | const encoded3 = lookup[base64.charCodeAt(i + 2)]; 52 | const encoded4 = lookup[base64.charCodeAt(i + 3)]; 53 | 54 | bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); 55 | bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); 56 | bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); 57 | } 58 | 59 | return bytes; 60 | } 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "friendly-pow", 3 | "version": "0.2.2", 4 | "description": "Friendly WASM & JS Proof of Work algorithms for CAPTCHA", 5 | "author": "Guido Zuidhof ", 6 | "repository": "https://github.com/FriendlyCaptcha/friendly-pow", 7 | "scripts": { 8 | "prebuild": "rimraf dist", 9 | "build:as:untouched": "asc src/entryWasm.ts -b dist/wasm/untouched.wasm -t dist/wasm/untouched.wat -d dist/wasm/untouched.d.ts --runtime stub --sourceMap --debug", 10 | "build:as:optimized": "asc src/entryWasm.ts -b dist/wasm/optimized.wasm -t dist/wasm/optimized.wat -d dist/wasm/optimized.d.ts --runtime stub -O3 --noAssert", 11 | "build:as": "npm run build:as:untouched && npm run build:as:optimized", 12 | "wrap:as:optimized": "wasmwrap --input dist/wasm/optimized.wasm dist --output dist/wasm/optimized.wrap.ts --no-include-decode", 13 | "wrap:as:untouched": "wasmwrap --input dist/wasm/untouched.wasm dist --output dist/wasm/untouched.wrap.ts --no-include-decode", 14 | "wrap:as": "npm run wrap:as:untouched && npm run wrap:as:optimized", 15 | "build:ts": "tsc", 16 | "build:ts:node": "tsc --project tsconfig.common.json", 17 | "build": "npm run prebuild && npm run build:as:optimized && npm run wrap:as:optimized && npm run build:ts && npm run build:ts:node && cp package.json dist/package.json", 18 | "test": "npm run test:as && npm run test:ts", 19 | "test:as": "node ./node_modules/@as-pect/cli/bin/asp --verbose", 20 | "test:ts": "jest --coverage", 21 | "prepublishOnly": "echo \"Error: Don't run 'npm publish' in root. Use 'npm run dist' instead.\" && exit 1", 22 | "dist": "npm i && npm run build && cd dist && npm publish --ignore-scripts", 23 | "lint": "eslint src/**/*.ts test/**/*.ts" 24 | }, 25 | "devDependencies": { 26 | "@as-pect/cli": "^4.0.0", 27 | "@babel/cli": "^7.10.4", 28 | "@babel/preset-env": "^7.10.4", 29 | "@types/jest": "^27.0.2", 30 | "@types/node": "^14.0.11", 31 | "@typescript-eslint/eslint-plugin": "^2.21.0", 32 | "@typescript-eslint/parser": "^2.21.0", 33 | "assemblyscript": "^0.16.1", 34 | "eslint": "^8.1.0", 35 | "fast-async": "^6.3.8", 36 | "jest": "^27.3.1", 37 | "rimraf": "^3.0.2", 38 | "rollup": "^2.21.0", 39 | "rollup-plugin-typescript2": "^0.27.1", 40 | "ts-jest": "^27.0.7", 41 | "typescript": "^3.6.3", 42 | "wasmwrap": "^1.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/blake2b64.astest.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/camelcase */ 2 | 3 | import * as b32 from "../src/blake2b/blake2b"; 4 | import * as b64 from "../src/blake2b/blake2b64"; 5 | import { fromHexString } from "./blake2b.test"; 6 | 7 | describe("blake2b 32 and 64 bit equivalence", () => { 8 | test("compress empty", () => { 9 | const ctxb32 = b32.blake2bInit(64, null); 10 | const ctxb64 = b64.blake2bInit(64, null); 11 | 12 | const ctxb64_v = [ 13 | ctxb64.v0, 14 | ctxb64.v1, 15 | ctxb64.v2, 16 | ctxb64.v3, 17 | ctxb64.v4, 18 | ctxb64.v5, 19 | ctxb64.v6, 20 | ctxb64.v7, 21 | ]; 22 | 23 | for (let i = 0; i < 8; i++) { 24 | // combine two u32 into one u64 25 | const h: u64 = ((ctxb32.h[i * 2 + 1] as u64) << 32) ^ (ctxb32.h[i * 2] as u64); 26 | const v: u64 = ((ctxb32.v[i * 2 + 1] as u64) << 32) ^ (ctxb32.v[i * 2] as u64); 27 | expect(h).toStrictEqual(ctxb64.h[i]); 28 | expect(v).toStrictEqual(ctxb64_v[i]); 29 | } 30 | 31 | for (let final = 0; final < 2; final++) { 32 | b32.blake2bCompress(ctxb32, final == 1); 33 | b64.blake2bCompress(ctxb64, final == 1, load(changetype(ctxb64.b))); 34 | 35 | const ctxb64_v = [ 36 | ctxb64.v0, 37 | ctxb64.v1, 38 | ctxb64.v2, 39 | ctxb64.v3, 40 | ctxb64.v4, 41 | ctxb64.v5, 42 | ctxb64.v6, 43 | ctxb64.v7, 44 | ]; 45 | 46 | for (let i = 0; i < 8; i++) { 47 | // combine two u32 into one u64 48 | const h: u64 = ((ctxb32.h[i * 2 + 1] as u64) << 32) ^ (ctxb32.h[i * 2] as u64); 49 | const v: u64 = ((ctxb32.v[i * 2 + 1] as u64) << 32) ^ (ctxb32.v[i * 2] as u64); 50 | const m: u64 = ((ctxb32.m[i * 2 + 1] as u64) << 32) ^ (ctxb32.m[i * 2] as u64); 51 | expect(h).toStrictEqual(ctxb64.h[i], "h" + final.toString()); 52 | expect(v).toStrictEqual(ctxb64_v[i], "v" + final.toString()); 53 | expect(m).toStrictEqual(ctxb64.m[i], "m" + final.toString()); 54 | } 55 | } 56 | }); 57 | }); 58 | 59 | describe("blake64 implementation follows spec", () => { 60 | test("get blake2b spec hashes", () => { 61 | const expectedEmptyHash = fromHexString( 62 | "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce" 63 | ); 64 | expect(b64.blake2b(new Uint8Array(0))).toStrictEqual(expectedEmptyHash); 65 | 66 | const abc = new Uint8Array(3); 67 | abc.set([97, 98, 99]); //abc 68 | const expectedAbcHash = fromHexString( 69 | "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923" 70 | ); 71 | expect(b64.blake2b(abc)).toStrictEqual(expectedAbcHash); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /src/loader.ts: -------------------------------------------------------------------------------- 1 | import ASModule from "../dist/wasm/optimized"; 2 | 3 | declare const WebAssembly: any; 4 | 5 | // This is a hand-pruned version of the assemblyscript loader, removing 6 | // a lot of functionality we don't need, saving in bundle size. 7 | function addUtilityExports(instance: any) { 8 | const extendedExports: any = {}; 9 | const exports = instance.exports; 10 | const memory = exports.memory; 11 | const alloc = exports["__alloc"]; 12 | const retain = exports["__retain"]; 13 | const rttiBase = exports["__rtti_base"] || ~0; // oob if not present 14 | 15 | /** Gets the runtime type info for the given id. */ 16 | function getInfo(id: usize) { 17 | const U32 = new Uint32Array(memory.buffer); 18 | // const count = U32[rttiBase >>> 2]; 19 | // if ((id >>>= 0) >= count) throw Error("invalid id: " + id); 20 | return U32[((rttiBase + 4) >>> 2) + id * 2]; 21 | } 22 | 23 | /** Allocates a new array in the module's memory and returns its retained pointer. */ 24 | extendedExports.__allocArray = (id: usize, values: any) => { 25 | const info = getInfo(id); 26 | const align = 31 - Math.clz32((info >>> 6) & 31); 27 | const length = values.length; 28 | const buf = alloc(length << align, 0); 29 | const arr = alloc(12, id); 30 | const U32 = new Uint32Array(memory.buffer); 31 | U32[(arr + 0) >>> 2] = retain(buf); 32 | U32[(arr + 4) >>> 2] = buf; 33 | U32[(arr + 8) >>> 2] = length << align; 34 | const buffer = memory.buffer; 35 | const view = new Uint8Array(buffer); 36 | if (info & (1 << 14)) { 37 | for (let i = 0; i < length; ++i) view[(buf >>> align) + i] = retain(values[i]); 38 | } else { 39 | view.set(values, buf >>> align); 40 | } 41 | return arr; 42 | }; 43 | 44 | extendedExports.__getUint8Array = (ptr: number) => { 45 | const U32 = new Uint32Array(memory.buffer); 46 | const bufPtr = U32[(ptr + 4) >>> 2]; 47 | return new Uint8Array(memory.buffer, bufPtr, U32[(bufPtr - 4) >>> 2] >>> 0); 48 | }; 49 | return demangle(exports, extendedExports); 50 | } 51 | 52 | /** Demangles an AssemblyScript module's exports to a friendly object structure. */ 53 | function demangle(exports: any, extendedExports: any = {}) { 54 | // extendedExports = Object.create(extendedExports); 55 | 56 | const setArgumentsLength = exports["__argumentsLength"] 57 | ? (length: any) => { 58 | exports["__argumentsLength"].value = length; 59 | } 60 | : exports["__setArgumentsLength"] || 61 | exports["__setargc"] || 62 | (() => { 63 | return {}; 64 | }); 65 | for (const internalName in exports) { 66 | if (!Object.prototype.hasOwnProperty.call(exports, internalName)) continue; 67 | const elem = exports[internalName]; 68 | 69 | // Only necessary if nested exports are present 70 | // let parts = internalName.split("."); 71 | // let curr = extendedExports; 72 | // while (parts.length > 1) { 73 | // let part = parts.shift(); 74 | // if (!Object.prototype.hasOwnProperty.call(curr, part as any)) curr[part as any] = {}; 75 | // curr = curr[part as any]; 76 | // } 77 | 78 | const name = internalName.split(".")[0]; 79 | 80 | if (typeof elem === "function" && elem !== setArgumentsLength) { 81 | ( 82 | (extendedExports[name] = (...args: any[]) => { 83 | setArgumentsLength(args.length); 84 | return elem(...args); 85 | }) as any 86 | ).original = elem; 87 | } else { 88 | extendedExports[name] = elem; 89 | } 90 | } 91 | return extendedExports; 92 | } 93 | 94 | export async function instantiateWasmSolver( 95 | module: any 96 | ): Promise<{ exports: /*(ASUtil.ASUtil & ResultObject */ typeof ASModule & any }> { 97 | const imports: any = { 98 | env: { 99 | abort() { 100 | throw Error("Wasm aborted"); 101 | }, 102 | }, 103 | }; 104 | 105 | const result = await WebAssembly.instantiate(module, imports); 106 | const exports = addUtilityExports(result); 107 | 108 | return { exports } as any; 109 | } 110 | -------------------------------------------------------------------------------- /test/blake2b.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | blake2b, 3 | B2B_G, 4 | blake2bInit, 5 | blake2bCompress, 6 | blake2bUpdate, 7 | B2B_GET32, 8 | ADD64AA, 9 | ADD64AC, 10 | } from "../src/blake2b/blake2b"; 11 | 12 | export function fromHexString(s: string): Uint8Array { 13 | const bytes = new Uint8Array(Math.ceil((s.length as f64) / 2) as u32); 14 | for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(s.substr(i * 2, 2), 16) as u32; 15 | return bytes; 16 | } 17 | 18 | describe("blake2b hashing", () => { 19 | test("get blake2b spec hashes", () => { 20 | const expectedEmptyHash = fromHexString( 21 | "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce" 22 | ); 23 | expect(blake2b(new Uint8Array(0))).toStrictEqual(expectedEmptyHash); 24 | 25 | const abc = new Uint8Array(3); 26 | abc.set([97, 98, 99]); //abc 27 | const expectedAbcHash = fromHexString( 28 | "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923" 29 | ); 30 | expect(blake2b(abc)).toStrictEqual(expectedAbcHash); 31 | }); 32 | 33 | test("get u32 from 4xu8", () => { 34 | const v = new Uint8Array(4); 35 | v[1] = 255; 36 | expect(B2B_GET32(v, 0)).toStrictEqual(65280); 37 | }); 38 | 39 | test("64-bit unsigned addition within array", () => { 40 | const input = new Uint32Array(4); 41 | input.set([0xffffffff, 5, 1, 1]); 42 | ADD64AA(input, 0, 2); 43 | expect(input[0]).toStrictEqual(0); 44 | expect(input[1]).toStrictEqual(7); 45 | }); 46 | 47 | test("64-bit unsigned addition scalar", () => { 48 | const input = new Uint32Array(4); 49 | input.set([0xffffffff, 5]); 50 | ADD64AC(input, 0, 2, 1); 51 | expect(input[0]).toStrictEqual(1); 52 | expect(input[1]).toStrictEqual(7); 53 | }); 54 | 55 | // Note: All remaining tests here are not very insightful, they merely compare typescript vs webassembly build outputs. 56 | 57 | test("G mixing function", () => { 58 | const v = new Uint32Array(32); 59 | v.set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); 60 | v.set([0, 2 ^ 32, 0, 123, 12345678, 5, 6, 7, 8, 9, 10, 11, 12, 13, 2 ^ 31, 2 ^ (32 - 1)]); 61 | const m = new Uint32Array(32); 62 | 63 | const a = 0; 64 | const b = 8; 65 | const c = 16; 66 | const d = 24; 67 | 68 | const ix = 2; 69 | const iy = 4; 70 | 71 | B2B_G(v, m, a, b, c, d, ix, iy); 72 | 73 | // Copy pasted from TS build 74 | expect(v.toString()).toStrictEqual( 75 | "264,9003,0,123,12345678,5,6,7,1178993238,38159888,10,11,12,13,29,29,589496363,19070984,0,0,0,0,0,0,589496320,19070976,0,0,0,0,0,0" 76 | ); 77 | }); 78 | 79 | test("init", () => { 80 | const ctx = blake2bInit(64, null); 81 | 82 | expect(ctx.b).toStrictEqual(new Uint8Array(128)); 83 | expect(ctx.h[0]).toStrictEqual(4072524104); 84 | expect(ctx.h[1]).toStrictEqual(1779033703); 85 | expect(ctx.t).toStrictEqual(0); 86 | expect(ctx.c).toStrictEqual(0); 87 | expect(ctx.outlen).toStrictEqual(64); 88 | expect(ctx.v).toStrictEqual(new Uint32Array(32)); 89 | }); 90 | 91 | test("compress empty", () => { 92 | const ctx = blake2bInit(64, null); 93 | 94 | blake2bCompress(ctx, false); 95 | 96 | // Copy pasted from TS build 97 | const expectedV = new Uint32Array(32); 98 | expectedV.set([ 99 | 772608607, 2130132885, 2933656603, 980914306, 959117553, 4176783481, 1815619133, 1815570653, 100 | 1821205348, 1568854325, 1456005267, 2693368475, 3725261559, 3408072416, 2935537161, 101 | 3038017681, 2998450602, 3154653318, 3777044161, 3761885341, 3149654422, 1015042019, 102 | 1559909578, 2433118349, 3370344048, 3464657500, 1094075388, 2013395361, 2772220844, 103 | 3720830868, 462607019, 2884304071, 104 | ]); 105 | 106 | const expectedH = new Uint32Array(16); 107 | expectedH.set([ 108 | 1846043325, 2834753908, 3409425889, 1630138010, 2080762188, 4162458856, 1876902918, 109 | 1484713322, 159956933, 3255761174, 1019268976, 1133321142, 2155346992, 157780191, 2786348507, 110 | 1159612751, 111 | ]); 112 | 113 | expect(ctx.v).toStrictEqual(expectedV); 114 | expect(ctx.h).toStrictEqual(expectedH); 115 | }); 116 | 117 | test("compress last", () => { 118 | const ctx = blake2bInit(64, null); 119 | 120 | blake2bCompress(ctx, true); 121 | 122 | // Copy pasted from TS build 123 | const expectedV = new Uint32Array(32); 124 | expectedV.set([ 125 | 4031842588, 4034117626, 1105205952, 716066648, 1266876956, 3531600776, 34974238, 3462341121, 126 | 11388459, 130607887, 740867744, 4234459681, 744709436, 1079930893, 3345850578, 4003144585, 127 | 4126059052, 2569230559, 1087863613, 3810243576, 4115715494, 2404800539, 1256856165, 128 | 1917110476, 2623216168, 94263263, 1131568044, 751934526, 2353715911, 174776754, 3462655614, 129 | 2073654638, 130 | ]); 131 | 132 | const expectedH = new Uint32Array(16); 133 | expectedH.set([ 134 | 4144130680, 56164674, 2248001222, 1926386213, 1078407057, 1632065761, 400721546, 424943607, 135 | 823156434, 1398337199, 1147439379, 1269845651, 1533557392, 1438074900, 443576277, 3470957566, 136 | ]); 137 | expect(ctx.v).toStrictEqual(expectedV); 138 | expect(ctx.h).toStrictEqual(expectedH); 139 | }); 140 | 141 | test("e2e tiny", () => { 142 | const input = new Uint8Array(2); 143 | input.set([0, 255]); 144 | const hash = blake2b(input); 145 | const expectedHash = new Uint8Array(64); 146 | expectedHash.set([ 147 | 208, 234, 198, 36, 197, 41, 127, 189, 143, 212, 21, 104, 255, 238, 57, 183, 87, 229, 220, 88, 148 | 191, 158, 133, 39, 124, 102, 185, 51, 196, 84, 180, 135, 255, 102, 223, 33, 223, 120, 45, 71, 149 | 70, 100, 87, 230, 202, 242, 2, 37, 28, 76, 47, 190, 177, 96, 125, 80, 74, 4, 51, 169, 18, 39, 150 | 251, 243, 151 | ]); 152 | expect(hash).toStrictEqual(expectedHash); 153 | }); 154 | 155 | test("update large", () => { 156 | const ctx = blake2bInit(64, null); 157 | const input = new Uint8Array(256); 158 | input.set([0, 1, 2, 3, 4, 5, 6, 7]); 159 | input[127] = 255; 160 | input[255] = 255; 161 | blake2bUpdate(ctx, input); 162 | 163 | const expectedV = new Uint32Array(32); 164 | expectedV.set([ 165 | 2391667886, 106264524, 3172067745, 843186038, 3916035669, 2460205062, 1049526093, 2769672167, 166 | 2003188517, 1941229264, 2886399137, 3747394009, 1517203667, 3555690808, 747297094, 2723106018, 167 | 1691437400, 1013408666, 1955360162, 4042312437, 1916695314, 3025236654, 3503036709, 168 | 3339670097, 1952172470, 1302250944, 2883393958, 310728088, 2835175918, 2296442917, 2825290748, 169 | 2102536207, 170 | ]); 171 | const expectedH = new Uint32Array(16); 172 | expectedH.set([ 173 | 417428670, 1346105905, 1297556280, 2044107526, 1707310444, 446448090, 2975813785, 3344265356, 174 | 2933616706, 1864653167, 753500440, 1457294541, 164779094, 1150066870, 2542959555, 2231216628, 175 | ]); 176 | expect(ctx.v).toStrictEqual(expectedV); 177 | expect(ctx.h).toStrictEqual(expectedH); 178 | }); 179 | }); 180 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 👾 Friendly Proof of Work (PoW) 2 | 3 | The PoW challenge library used by Friendly Captcha. 4 | 5 | If you are looking for the client code (=widget) and documentation, see the [**friendly-challenge repository**](https://github.com/FriendlyCaptcha/friendly-challenge). 6 | 7 | Friendly-pow has a puzzle solver implementation in both WASM and Javascript (as a fallback for very old browsers). The WASM build is an order of magnitude faster in most browsers. 8 | 9 | In this document we will call the computational challenge the computer has to solve the **puzzle**, the client has to find one or more **solutions** that can later be verified. 10 | 11 | ## Implementation 12 | 13 | The WASM is compiled from AssemblyScript, which is more or less a subset of Typescript. The WASM build entrypoint is found at [`src/entryWasm.ts`](./src/entryWasm.ts). The wrappers for the JS and WASM build are found in the `src/api` folder, they both have the same interface. 14 | 15 | ## Building this project 16 | 17 | Building this project can be tricky, it currently uses an Assemblyscript version that is not the latest. The loader will have to be changed to account for breaking changes if we want to update. The current loader has custom modifications to get bundle size down considerably, something that will need to happen for an upgraded version too. Currently there is little gain if we do so. 18 | 19 | ``` 20 | npm i --legacy-peer-deps 21 | npm run build 22 | ``` 23 | 24 | ## Puzzle format 25 | 26 | The puzzle is represented as a 32 byte to 64 byte message, let's call this the puzzle buffer. Encoded in this byte buffer is metadata such as when the puzzle was created, its difficulty, and a cryptographically random nonce, this is described in more detail later. 27 | 28 | A solution consists of one or more 8 byte values. 29 | 30 | ### Puzzle difficulty 31 | 32 | The difficulty of a puzzle is encoded in the challenge buffer as a single byte `d`. The _difficulty threshold_ `T` is computed using the formula: 33 | 34 | ``` 35 | T = floor(2^((255.999-d)/8))) 36 | ``` 37 | 38 | In Javascript this is defined as: 39 | 40 | ```javascript 41 | const T = Math.pow(2, (255.999 - d) / 8.0) >>> 0; // >>> 0 forces it to unsigned 32bit value 42 | ``` 43 | 44 | ### Solving the puzzle 45 | 46 | First the puzzle buffer is padded with zeroes until it is 128 bytes long. Then the goal is to change the last 8 bytes of this buffer (which we will call the `solution`) until the [`blake2b-256`]() hash of this buffer meets this criteria: 47 | 48 | The first 4 bytes of the `blake2b-256` hash are read as a little-endian unsigned 32 bit integer. If this integer smaller than the _difficulty threshold_ `T` for the puzzle, the `solution` is **valid**. 49 | 50 | The only way to solve the puzzle is to exhaustively try different solutions. The probability of getting a solution per attempt is given by `T/(2^32-1)` (note the denominator is the maximum uint32 value), the expected number of attempts required is the inverse of that probability. 51 | 52 | ### Multiple puzzles 53 | 54 | One could get lucky or unlucky in how many attempts are required to find a solution. In order to reduce the variance and allow us to show a progress bar to the user we actually have the client find multiple solutions (with a lower difficulty threshold). This number of solutions (`n`) required is also encoded in the puzzle buffer as single byte value. 55 | 56 | The final solution to the whole puzzle are `n` of these 8 byte `solutions` concatenated together. 57 | 58 | ## Puzzle format 59 | 60 | The puzzle is described by a byte array up to 64 bytes (the `puzzle buffer`). It consists of 61 | 62 | ``` 63 | The input to solve consists of bytes (and their corresponding trailing offsets AKA cumulative sum of bytes) 64 | * 4 (Puzzle Timestamp) | 4 65 | * 4 (Account ID) | 8 66 | * 4 (App ID) | 12 67 | * 1 (Puzzle version) | 13 68 | * 1 (Puzzle expiry) | 14 69 | * 1 (Number of solutions) | 15 70 | * 1 (Puzzle difficulty) | 16 71 | * 8 (Reserved, 0 for now) | 24 72 | * 8 (Puzzle Nonce) | 32 73 | * 32 (Optional additional data) | 64 74 | 75 | Or as characters (without additional data): 76 | tttt aaaa bbbb v e n d 00000000 pppppppp 77 | ``` 78 | 79 | The timestamp is in seconds since unix epoch (as unsigned 32 bit integer), the challenge nonce is a cryptographically secure random 8 byte value. 80 | 81 | When sent over the network the puzzle is base64 encoded. Along with the puzzle buffer a signature is also sent (more details below). The mesage consists of those two parts with a `.` separating them: 82 | 83 | ``` 84 | . 85 | ``` 86 | 87 | ### Puzzle expiry 88 | 89 | This byte encodes how long a solution for the puzzle can be submitted for. The duration in seconds is derived from this byte multiplying it by 300 (=5 minutes): 90 | 91 | ``` 92 | expiration_in_seconds = expiration_byte_value * 300 93 | ``` 94 | 95 | This implies that the maximum possible time is 21 hours and 15 minutes of validity. A value of 0 means the challenge does not expire (not recommended as you would have to keep the token indefinitely to prevent replay attacks, in Friendly Captcha this is never the case). 96 | 97 | The client widget is responsible for fetching and solving another puzzle if theirs is about to expire. 98 | 99 | ### Puzzle version 100 | 101 | For this repository the value is always 1. 102 | 103 | ### Additional user data 104 | 105 | There is a 32 byte space left over that could be used to add additional data that can later be verified. 106 | 107 | ### Signature 108 | 109 | The signature is the first part of the puzzle string, it is only signed and verified on the server. Any cryptographic signing algorithm can be used, generally it is hex encoded but it doesn't have to be. 110 | 111 | ## Solution format 112 | 113 | Repeating from above: a number of solutions is required for the puzzle (up to 255), and every solution is an 8 byte value. These 8 byte solutions are concatenated together into one byte array. The solution payload is a string that consists of the signature, puzzle, solutions and diagnostics (detailed below), the last 3 of these are base64 encoded, with a `.` separating them: 114 | 115 | ``` 116 | ... 117 | ``` 118 | 119 | ## Diagnostics 120 | 121 | The client sends 3 bytes of information that helps give insight into what the experience is like for users (in particular how long it takes), this allows Friendly Captcha to tweak the difficulty to what makes sense for the audience. 122 | 123 | ``` 124 | byte 0: what solver was used, 0 for unspecified, 1 for JS fallback, 2 for WASM32. 125 | byte 1-2: how many seconds the solve took in total as an unsigned 16 bit integer. 126 | ``` 127 | 128 | ## Verification 129 | 130 | Verification can be done as follows: 131 | 132 | - **Integrity**: The puzzle buffer integrity is verified by checking that the signature matches. 133 | - **Check Account and App ID**: Check whether the puzzle was for this account + website (section). 134 | - **Expiration**: Timestamp and expiry time is compared to current time. 135 | - **Version**: The version of the puzzle is checked to be sure we support that version. 136 | - **Solution Count**: Check that we have enough solutions to the puzzle and that there are no duplicates. 137 | - **Replay check**: Check whether this puzzle has been submitted before. 138 | - **Solution verification**: Each of the solutions are verified to produce a correct hash given the difficulty. 139 | 140 | These steps can happen in any order. 141 | 142 | ## License 143 | 144 | Friendly Captcha License; source-available only 145 | -------------------------------------------------------------------------------- /src/blake2b/blake2b64.ts: -------------------------------------------------------------------------------- 1 | // Blake2B in assemblyscript, 64bit version 2 | 3 | // @ts-ignore 4 | @unmanaged 5 | export class Context { 6 | b: Uint8Array = new Uint8Array(128); 7 | h: Uint64Array = new Uint64Array(8); 8 | t: u64 = 0; // input count 9 | c: u32 = 0; // pointer within buffer 10 | 11 | v0: u64 = 0; 12 | v1: u64 = 0; 13 | v2: u64 = 0; 14 | v3: u64 = 0; 15 | v4: u64 = 0; 16 | v5: u64 = 0; 17 | v6: u64 = 0; 18 | v7: u64 = 0; 19 | v8: u64 = 0; 20 | v9: u64 = 0; 21 | v10: u64 = 0; 22 | v11: u64 = 0; 23 | v12: u64 = 0; 24 | v13: u64 = 0; 25 | v14: u64 = 0; 26 | v15: u64 = 0; 27 | 28 | m: StaticArray = new StaticArray(16); 29 | 30 | constructor(public outlen: u32) {} 31 | } 32 | 33 | // Initialization Vector 34 | const BLAKE2B_IV_0: u64 = 0x6a09e667f3bcc908; 35 | const BLAKE2B_IV_1: u64 = 0xbb67ae8584caa73b; 36 | const BLAKE2B_IV_2: u64 = 0x3c6ef372fe94f82b; 37 | const BLAKE2B_IV_3: u64 = 0xa54ff53a5f1d36f1; 38 | const BLAKE2B_IV_4: u64 = 0x510e527fade682d1; 39 | const BLAKE2B_IV_5: u64 = 0x9b05688c2b3e6c1f; 40 | const BLAKE2B_IV_6: u64 = 0x1f83d9abfb41bd6b; 41 | const BLAKE2B_IV_7: u64 = 0x5be0cd19137e2179; 42 | 43 | const SIGMA8 = memory.data([ 44 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 45 | 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, 46 | 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, 47 | 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, 48 | 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, 49 | 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, 50 | 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, 51 | 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, 52 | 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, 53 | 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, 54 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 55 | 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 56 | ]); 57 | 58 | // Compression function. 'last' flag indicates last block. 59 | export function blake2bCompress(ctx: Context, last: bool, bBufferPtr: usize): void { 60 | const m = ctx.m; 61 | const h = ctx.h; 62 | const mPtr = changetype(m); 63 | 64 | // high 64 bits not supported, offset may not be higher than 2**53-1 65 | // const u = Uint64Array.wrap(ctx.b.buffer); 66 | // get little-endian words 67 | for (let i = 0; i < 16 * 8; i += 8) { 68 | //m[i] = u[i] 69 | store(mPtr + i, load(bBufferPtr + i)); 70 | } 71 | 72 | let v0 = unchecked(h[0]); 73 | let v1 = unchecked(h[1]); 74 | let v2 = unchecked(h[2]); 75 | let v3 = unchecked(h[3]); 76 | let v4 = unchecked(h[4]); 77 | let v5 = unchecked(h[5]); 78 | let v6 = unchecked(h[6]); 79 | let v7 = unchecked(h[7]); 80 | let v8 = BLAKE2B_IV_0; 81 | let v9 = BLAKE2B_IV_1; 82 | let v10 = BLAKE2B_IV_2; 83 | let v11 = BLAKE2B_IV_3; 84 | let v12 = BLAKE2B_IV_4 ^ ctx.t; // low 64 bits of offset 85 | let v13 = BLAKE2B_IV_5; 86 | let v14 = last ? ~BLAKE2B_IV_6 : BLAKE2B_IV_6; // last block flag set ? 87 | let v15 = BLAKE2B_IV_7; 88 | 89 | let x: u64, y: u64; 90 | 91 | for (let o = 0; o < 12 * 16; o += 16) { 92 | const sigmaPtr = SIGMA8 + o; 93 | // mix(vPtr, 0*8, 4*8, 8*8, 12*8, m[SIGMA8[o + 0]], m[SIGMA8[o + 1]]) 94 | x = unchecked(m[load(sigmaPtr + 0)]); 95 | y = unchecked(m[load(sigmaPtr + 1)]); 96 | v0 += v4 + x; v12 = rotr(v12 ^ v0, 32); 97 | v8 += v12; v4 = rotr( v4 ^ v8, 24); 98 | v0 += v4 + y; v12 = rotr(v12 ^ v0, 16); 99 | v8 += v12; v4 = rotr( v4 ^ v8, 63); 100 | 101 | // mix(vPtr, 1*8, 5*8, 9*8, 13*8, m[SIGMA8[o + 2]], m[SIGMA8[o + 3]]) 102 | x = unchecked(m[load(sigmaPtr + 2)]); 103 | y = unchecked(m[load(sigmaPtr + 3)]); 104 | v1 += v5 + x; v13 = rotr(v13 ^ v1, 32); 105 | v9 += v13; v5 = rotr( v5 ^ v9, 24); 106 | v1 += v5 + y; v13 = rotr(v13 ^ v1, 16); 107 | v9 += v13; v5 = rotr( v5 ^ v9, 63); 108 | 109 | // mix(vPtr, 2*8, 6*8, 10*8, 14*8, m[SIGMA8[o + 4]], m[SIGMA8[o + 5]]) 110 | x = unchecked(m[load(sigmaPtr + 4)]); 111 | y = unchecked(m[load(sigmaPtr + 5)]); 112 | v2 += v6 + x; v14 = rotr(v14 ^ v2, 32); 113 | v10 += v14; v6 = rotr( v6 ^ v10, 24); 114 | v2 += v6 + y; v14 = rotr(v14 ^ v2, 16); 115 | v10 += v14; v6 = rotr( v6 ^ v10, 63); 116 | 117 | // mix(vPtr, 3*8, 7*8, 11*8, 15*8, m[SIGMA8[o + 6]], m[SIGMA8[o + 7]]) 118 | x = unchecked(m[load(sigmaPtr + 6)]); 119 | y = unchecked(m[load(sigmaPtr + 7)]); 120 | v3 += v7 + x; v15 = rotr(v15 ^ v3, 32); 121 | v11 += v15; v7 = rotr( v7 ^ v11, 24); 122 | v3 += v7 + y; v15 = rotr(v15 ^ v3, 16); 123 | v11 += v15; v7 = rotr( v7 ^ v11, 63); 124 | 125 | // mix(vPtr, 0*8, 5*8, 10*8, 15*8, m[SIGMA8[o + 8]], m[SIGMA8[o + 9]]) 126 | x = unchecked(m[load(sigmaPtr + 8)]); 127 | y = unchecked(m[load(sigmaPtr + 9)]); 128 | v0 += v5 + x; v15 = rotr(v15 ^ v0, 32); 129 | v10 += v15; v5 = rotr( v5 ^ v10, 24); 130 | v0 += v5 + y; v15 = rotr(v15 ^ v0, 16); 131 | v10 += v15; v5 = rotr( v5 ^ v10, 63); 132 | 133 | // mix(vPtr, 1*8, 6*8, 11*8, 12*8, m[SIGMA8[o + 10]], m[SIGMA8[o + 11]]) 134 | x = unchecked(m[load(sigmaPtr + 10)]); 135 | y = unchecked(m[load(sigmaPtr + 11)]); 136 | v1 += v6 + x; v12 = rotr(v12 ^ v1, 32); 137 | v11 += v12; v6 = rotr( v6 ^ v11, 24); 138 | v1 += v6 + y; v12 = rotr(v12 ^ v1, 16); 139 | v11 += v12; v6 = rotr( v6 ^ v11, 63); 140 | 141 | // mix(vPtr, 2*8, 7*8, 8*8, 13*8, m[SIGMA8[o + 12]], m[SIGMA8[o + 13]]) 142 | x = unchecked(m[load(sigmaPtr + 12)]); 143 | y = unchecked(m[load(sigmaPtr + 13)]); 144 | v2 += v7 + x; v13 = rotr(v13 ^ v2, 32); 145 | v8 += v13; v7 = rotr( v7 ^ v8, 24); 146 | v2 += v7 + y; v13 = rotr(v13 ^ v2, 16); 147 | v8 += v13; v7 = rotr( v7 ^ v8, 63); 148 | 149 | // mix(vPtr, 3*8, 4*8, 9*8, 14*8, m[SIGMA8[o + 14]], m[SIGMA8[o + 15]]) 150 | x = unchecked(m[load(sigmaPtr + 14)]); 151 | y = unchecked(m[load(sigmaPtr + 15)]); 152 | v3 += v4 + x; v14 = rotr(v14 ^ v3, 32); 153 | v9 += v14; v4 = rotr( v4 ^ v9, 24); 154 | v3 += v4 + y; v14 = rotr(v14 ^ v3, 16); 155 | v9 += v14; v4 = rotr( v4 ^ v9, 63); 156 | } 157 | 158 | unchecked(h[0] ^= v0 ^ v8); 159 | unchecked(h[1] ^= v1 ^ v9); 160 | unchecked(h[2] ^= v2 ^ v10); 161 | unchecked(h[3] ^= v3 ^ v11); 162 | 163 | unchecked(h[4] ^= v4 ^ v12); 164 | unchecked(h[5] ^= v5 ^ v13); 165 | unchecked(h[6] ^= v6 ^ v14); 166 | unchecked(h[7] ^= v7 ^ v15); 167 | 168 | ctx.v0 = v0; 169 | ctx.v1 = v1; 170 | ctx.v2 = v2; 171 | ctx.v3 = v3; 172 | ctx.v4 = v4; 173 | ctx.v5 = v5; 174 | ctx.v6 = v6; 175 | ctx.v7 = v7; 176 | ctx.v8 = v8; 177 | ctx.v9 = v9; 178 | ctx.v10 = v10; 179 | ctx.v11 = v11; 180 | ctx.v12 = v12; 181 | ctx.v13 = v13; 182 | ctx.v14 = v14; 183 | ctx.v15 = v15; 184 | } 185 | 186 | // Creates a BLAKE2b hashing context 187 | // Requires an output length between 1 and 64 bytes 188 | // Takes an optional Uint8Array key 189 | export function blake2bInit(outlen: u32, key: Uint8Array | null): Context { 190 | if (outlen === 0 || outlen > 64) { 191 | throw new Error('Illegal output length, expected 0 < length <= 64'); 192 | } 193 | if (key !== null && key.length > 64) { 194 | throw new Error('Illegal key, expected Uint8Array with 0 < length <= 64'); 195 | } 196 | const ctx = new Context(outlen); 197 | const h = ctx.h; 198 | 199 | // Initialize State vector h with IV 200 | // Mix key size (cbKeyLen) and desired hash length (cbHashLen) into h0 201 | const keylen: u64 = key !== null ? key.length : 0; 202 | unchecked(h[0] = BLAKE2B_IV_0 ^ 0x01010000 ^ (keylen << 8) ^ (outlen as u64)); 203 | unchecked(h[1] = BLAKE2B_IV_1); 204 | unchecked(h[2] = BLAKE2B_IV_2); 205 | unchecked(h[3] = BLAKE2B_IV_3); 206 | unchecked(h[4] = BLAKE2B_IV_4); 207 | unchecked(h[5] = BLAKE2B_IV_5); 208 | unchecked(h[6] = BLAKE2B_IV_6); 209 | unchecked(h[7] = BLAKE2B_IV_7); 210 | 211 | // key the hash, if applicable 212 | if (key) { 213 | blake2bUpdate(ctx, key); 214 | // at the end 215 | ctx.c = 128; 216 | } 217 | 218 | return ctx; 219 | } 220 | 221 | // Updates a BLAKE2b streaming hash 222 | // Requires hash context and Uint8Array (byte array) 223 | export function blake2bUpdate(ctx: Context, input: Uint8Array): void { 224 | for (let i = 0, len = input.length; i < len; i++) { 225 | if (ctx.c === 128) { // buffer full ? 226 | ctx.t += ctx.c; // add counters 227 | blake2bCompress(ctx, false, load(changetype(ctx.b))); // compress (not last) 228 | ctx.c = 0; // counter to zero 229 | } 230 | unchecked(ctx.b[ctx.c++] = input[i]); 231 | } 232 | } 233 | 234 | // Completes a BLAKE2b streaming hash 235 | // Returns a Uint8Array containing the message digest 236 | export function blake2bFinal(ctx: Context): Uint8Array { 237 | ctx.t += ctx.c; // mark last block offset 238 | const tc = 128 - ctx.c; 239 | ctx.b.fill(0, ctx.c, tc); 240 | ctx.c += tc; 241 | blake2bCompress(ctx, true, load(changetype(ctx.b))); // final block flag = 1 242 | 243 | // little endian convert and store 244 | // const u64a = new Uint64Array(ctx.outlen / 8); 245 | // for(let i = 0; i < u64a.length; i++) { 246 | // unchecked(u64a[i] = ctx.h[i]); 247 | // } 248 | 249 | return Uint8Array.wrap(ctx.h.buffer, 0, ctx.outlen); 250 | } 251 | 252 | // Computes the BLAKE2B hash of a string or byte array, and returns a Uint8Array 253 | // 254 | // Returns a n-byte Uint8Array 255 | // 256 | // Parameters: 257 | // - input - the input bytes, as a string, Buffer or Uint8Array 258 | // - key - optional key Uint8Array, up to 64 bytes 259 | // - outlen - optional output length in bytes, default 64 260 | export function blake2b(input: Uint8Array, key: Uint8Array | null = null, outlen: u32 = 64): Uint8Array { 261 | const ctx = blake2bInit(outlen, key); 262 | blake2bUpdate(ctx, input); 263 | return blake2bFinal(ctx); 264 | } 265 | 266 | /** 267 | * FRIENDLYCAPTCHA optimization only, does not reset ctx.t (global byte counter), you need to do that yourself. 268 | * Assumes no key 269 | */ 270 | // @ts-ignore 271 | @inline 272 | export function blake2bResetForShortMessage(ctx: Context): void { 273 | const h = ctx.h; 274 | // Initialize State vector h with IV 275 | unchecked(h[0] = BLAKE2B_IV_0 ^ 0x01010000 ^ (ctx.outlen as u64)); 276 | unchecked(h[1] = BLAKE2B_IV_1); 277 | unchecked(h[2] = BLAKE2B_IV_2); 278 | unchecked(h[3] = BLAKE2B_IV_3); 279 | unchecked(h[4] = BLAKE2B_IV_4); 280 | unchecked(h[5] = BLAKE2B_IV_5); 281 | unchecked(h[6] = BLAKE2B_IV_6); 282 | unchecked(h[7] = BLAKE2B_IV_7); 283 | 284 | // Danger: These operations and resetting are really only possible because our input is exactly 128 bytes 285 | // for(let i = 0; i < 128; i++) { 286 | // ctx.b[i] = input[i]; 287 | // } 288 | // ctx.b = input; 289 | // We don't actually have to reset these as they are overwritten and we never stream.. 290 | // ctx.b.set(input); 291 | // ctx.m.fill(0); 292 | // ctx.v.fill(0); 293 | } 294 | -------------------------------------------------------------------------------- /src/blake2b/blake2b.ts: -------------------------------------------------------------------------------- 1 | // Blake2B made assemblyscript compatible, adapted from (CC0 licensed): 2 | // Blake2B in pure Javascript 3 | // Adapted from the reference implementation in RFC7693 4 | // Ported to Javascript by DC - https://github.com/dcposch 5 | 6 | export class Context { 7 | b: Uint8Array = new Uint8Array(128); 8 | h: Uint32Array = new Uint32Array(16); 9 | t: u64 = 0; // input count 10 | c: u32 = 0; // pointer within buffer 11 | outlen: u32; 12 | v: Uint32Array = new Uint32Array(32); 13 | m: Uint32Array = new Uint32Array(32); 14 | 15 | constructor(outlen: u32) { 16 | this.outlen = outlen; 17 | } 18 | } 19 | 20 | // 64-bit unsigned addition 21 | // Sets v[a,a+1] += v[b,b+1] 22 | // v should be a Uint32Array 23 | export function ADD64AA(v: Uint32Array, a: u32, b: u32): void { 24 | // Faster in Chrome.. 25 | const a0 = v[a]; 26 | const a1 = v[a + 1]; 27 | const b0 = v[b]; 28 | const b1 = v[b + 1]; 29 | 30 | const c0 = (a0 + b0) >>> 0; 31 | const c = ((a0 & b0) | ((a0 | b0) & ~c0)) >>> 31; 32 | 33 | v[a] = c0 as u32; 34 | v[a + 1] = ((a1 + b1 + c) >>> 0) as u32; 35 | 36 | // Alternative, faster in Firefox.. 37 | // const o0: u64 = (v[a] as u64) + (v[b] as u64); 38 | // let o1: u64 = (v[a + 1] as u64) + (v[b + 1] as u64); 39 | // if (o0 >= 0x100000000) { 40 | // o1++; 41 | // } 42 | // v[a] = o0 as u32; 43 | // v[a + 1] = o1 as u32; 44 | } 45 | 46 | // 64-bit unsigned addition 47 | // Sets v[a,a+1] += b 48 | // b0 is the low 32 bits of b, b1 represents the high 32 bits 49 | export function ADD64AC(v: Uint32Array, a: u32, b0: i64, b1: i64): void { 50 | // Faster in Chrome.. 51 | const a0 = v[a]; 52 | const a1 = v[a + 1]; 53 | 54 | const c0 = (a0 + b0) >>> 0; 55 | const c = ((a0 & b0) | ((a0 | b0) & ~c0)) >>> 31; 56 | v[a] = c0 as u32; 57 | v[a + 1] = ((a1 + b1 + c) >>> 0) as u32; 58 | 59 | // Alternative, faster in Firefox.. 60 | // let o0: i64 = (v[a] as i64) + b0; 61 | // if (b0 < 0) { 62 | // o0 += 0x100000000; 63 | // } 64 | // let o1: i64 = v[a + 1] + b1; 65 | // if (o0 >= 0x100000000) { 66 | // o1++; 67 | // } 68 | // v[a] = o0 as u32; 69 | // v[a + 1] = o1 as u32; 70 | } 71 | 72 | // Little-endian byte access 73 | export function B2B_GET32(arr: Uint8Array, i: u32): u32 { 74 | return ( 75 | (arr[i] as u32) ^ 76 | ((arr[i + 1] as u32) << 8) ^ 77 | ((arr[i + 2] as u32) << 16) ^ 78 | ((arr[i + 3] as u32) << 24) 79 | ); 80 | } 81 | 82 | // G Mixing function with everything inlined 83 | // performance at the cost of readability, especially faster in old browsers 84 | export function B2B_G_FAST( 85 | v: Uint32Array, 86 | m: Uint32Array, 87 | a: u32, 88 | b: u32, 89 | c: u32, 90 | d: u32, 91 | ix: u32, 92 | iy: u32 93 | ): void { 94 | const x0 = m[ix]; 95 | const x1 = m[ix + 1]; 96 | const y0 = m[iy]; 97 | const y1 = m[iy + 1]; 98 | 99 | // va0 are the low bits, va1 are the high bits 100 | let va0 = v[a]; 101 | let va1 = v[a + 1]; 102 | let vb0 = v[b]; 103 | let vb1 = v[b + 1]; 104 | let vc0 = v[c]; 105 | let vc1 = v[c + 1]; 106 | let vd0 = v[d]; 107 | let vd1 = v[d + 1]; 108 | 109 | let w0: u32, ww: u32, xor0: u32, xor1: u32; 110 | 111 | // ADD64AA(v, a, b); // v[a,a+1] += v[b,b+1] ... in JS we must store a uint64 as two uint32s 112 | // ADD64AA(v,a,b) 113 | w0 = va0 + vb0; 114 | ww = ((va0 & vb0) | ((va0 | vb0) & ~w0)) >>> 31; 115 | va0 = w0 as u32; 116 | va1 = (va1 + vb1 + ww) as u32; 117 | 118 | // // ADD64AC(v, a, x0, x1); // v[a, a+1] += x ... x0 is the low 32 bits of x, x1 is the high 32 bits 119 | w0 = va0 + x0; 120 | ww = ((va0 & x0) | ((va0 | x0) & ~w0)) >>> 31; 121 | va0 = w0 as u32; 122 | va1 = (va1 + x1 + ww) as u32; 123 | 124 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits 125 | xor0 = vd0 ^ va0; 126 | xor1 = vd1 ^ va1; 127 | // We can just swap high and low here becaeuse its a shift by 32 bits 128 | vd0 = xor1 as u32; 129 | vd1 = xor0 as u32; 130 | 131 | // ADD64AA(v, c, d); 132 | w0 = vc0 + vd0; 133 | ww = ((vc0 & vd0) | ((vc0 | vd0) & ~w0)) >>> 31; 134 | vc0 = w0 as u32; 135 | vc1 = (vc1 + vd1 + ww) as u32; 136 | 137 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits 138 | xor0 = vb0 ^ vc0; 139 | xor1 = vb1 ^ vc1; 140 | vb0 = (xor0 >>> 24) ^ (xor1 << 8); 141 | vb1 = (xor1 >>> 24) ^ (xor0 << 8); 142 | 143 | // ADD64AA(v, a, b); 144 | w0 = va0 + vb0; 145 | ww = ((va0 & vb0) | ((va0 | vb0) & ~w0)) >>> 31; 146 | va0 = w0 as u32; 147 | va1 = (va1 + vb1 + ww) as u32; 148 | 149 | // ADD64AC(v, a, y0, y1); 150 | w0 = va0 + y0; 151 | ww = ((va0 & y0) | ((va0 | y0) & ~w0)) >>> 31; 152 | va0 = w0 as u32; 153 | va1 = (va1 + y1 + ww) as u32; 154 | 155 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits 156 | xor0 = vd0 ^ va0; 157 | xor1 = vd1 ^ va1; 158 | vd0 = (xor0 >>> 16) ^ (xor1 << 16); 159 | vd1 = (xor1 >>> 16) ^ (xor0 << 16); 160 | 161 | // ADD64AA(v, c, d); 162 | w0 = vc0 + vd0; 163 | ww = ((vc0 & vd0) | ((vc0 | vd0) & ~w0)) >>> 31; 164 | vc0 = w0 as u32; 165 | vc1 = (vc1 + vd1 + ww) as u32; 166 | 167 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits 168 | xor0 = vb0 ^ vc0; 169 | xor1 = vb1 ^ vc1; 170 | vb0 = ((((xor1 as u64) >>> 31) as u64) ^ ((xor0 << 1) as u64)) as u32; 171 | vb1 = ((((xor0 as u64) >>> 31) as u64) ^ ((xor1 << 1) as u64)) as u32; 172 | 173 | v[a] = va0; 174 | v[a + 1] = va1; 175 | v[b] = vb0; 176 | v[b + 1] = vb1; 177 | v[c] = vc0; 178 | v[c + 1] = vc1; 179 | v[d] = vd0; 180 | v[d + 1] = vd1; 181 | } 182 | 183 | // G Mixing function 184 | // The ROTRs are inlined for speed 185 | export function B2B_G( 186 | v: Uint32Array, 187 | m: Uint32Array, 188 | a: u32, 189 | b: u32, 190 | c: u32, 191 | d: u32, 192 | ix: u32, 193 | iy: u32 194 | ): void { 195 | const x0 = m[ix]; 196 | const x1 = m[ix + 1]; 197 | const y0 = m[iy]; 198 | const y1 = m[iy + 1]; 199 | 200 | ADD64AA(v, a, b); // v[a,a+1] += v[b,b+1] ... in JS we must store a uint64 as two uint32s 201 | ADD64AC(v, a, x0, x1); // v[a, a+1] += x ... x0 is the low 32 bits of x, x1 is the high 32 bits 202 | 203 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits 204 | let xor0: u32 = v[d] ^ v[a]; 205 | let xor1: u32 = v[d + 1] ^ v[a + 1]; 206 | v[d] = xor1 as u32; 207 | v[d + 1] = xor0 as u32; 208 | 209 | ADD64AA(v, c, d); 210 | 211 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits 212 | xor0 = v[b] ^ v[c]; 213 | xor1 = v[b + 1] ^ v[c + 1]; 214 | v[b] = (xor0 >>> 24) ^ (xor1 << 8); 215 | v[b + 1] = (xor1 >>> 24) ^ (xor0 << 8); 216 | 217 | ADD64AA(v, a, b); 218 | ADD64AC(v, a, y0, y1); 219 | 220 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits 221 | xor0 = v[d] ^ v[a]; 222 | xor1 = v[d + 1] ^ v[a + 1]; 223 | v[d] = (xor0 >>> 16) ^ (xor1 << 16); 224 | v[d + 1] = (xor1 >>> 16) ^ (xor0 << 16); 225 | 226 | ADD64AA(v, c, d); 227 | 228 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits 229 | xor0 = v[b] ^ v[c]; 230 | xor1 = v[b + 1] ^ v[c + 1]; 231 | v[b] = ((((xor1 as u64) >>> 31) as u64) ^ ((xor0 << 1) as u64)) as u32; 232 | v[b + 1] = ((((xor0 as u64) >>> 31) as u64) ^ ((xor1 << 1) as u64)) as u32; 233 | } 234 | 235 | // Initialization Vector 236 | const BLAKE2B_IV32: u32[] = [ 237 | 0xf3bcc908, 0x6a09e667, 0x84caa73b, 0xbb67ae85, 0xfe94f82b, 0x3c6ef372, 0x5f1d36f1, 0xa54ff53a, 238 | 0xade682d1, 0x510e527f, 0x2b3e6c1f, 0x9b05688c, 0xfb41bd6b, 0x1f83d9ab, 0x137e2179, 0x5be0cd19, 239 | ]; 240 | 241 | // Note these offsets have all been multiplied by two to make them offsets into 242 | // a uint32 buffer. 243 | const SIGMA82: u8[] = [ 244 | 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, 245 | 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, 246 | 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, 247 | 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, 248 | 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, 249 | 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, 250 | 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, 251 | 2, 24, 0, 4, 22, 14, 10, 6, 252 | ]; 253 | 254 | // Compression function. 'last' flag indicates last block. 255 | export function blake2bCompress(ctx: Context, last: bool): void { 256 | const v = ctx.v; 257 | const m = ctx.m; 258 | 259 | // init work variables 260 | for (let i = 0; i < 16; i++) { 261 | v[i] = ctx.h[i]; 262 | v[i + 16] = BLAKE2B_IV32[i]; 263 | } 264 | 265 | // low 64 bits of offset 266 | v[24] = ((v[24] as u64) ^ ctx.t) as u32; 267 | v[25] = ((v[25] as u64) ^ ((ctx.t as u64) / 0x100000000)) as u32; 268 | // high 64 bits not supported, offset may not be higher than 2**53-1 269 | 270 | // last block flag set ? 271 | if (last) { 272 | v[28] = ~v[28]; 273 | v[29] = ~v[29]; 274 | } 275 | 276 | // get little-endian words 277 | for (let i = 0; i < 32; i++) { 278 | m[i] = B2B_GET32(ctx.b, 4 * i); 279 | } 280 | 281 | // twelve rounds of mixing 282 | for (let i = 0; i < 12; i++) { 283 | B2B_G_FAST(v, m, 0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]); 284 | B2B_G_FAST(v, m, 2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3]); 285 | B2B_G_FAST(v, m, 4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5]); 286 | B2B_G_FAST(v, m, 6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7]); 287 | B2B_G_FAST(v, m, 0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9]); 288 | B2B_G_FAST(v, m, 2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11]); 289 | B2B_G_FAST(v, m, 4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13]); 290 | B2B_G_FAST(v, m, 6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15]); 291 | } 292 | 293 | for (let i = 0; i < 16; i++) { 294 | ctx.h[i] = ctx.h[i] ^ v[i] ^ v[i + 16]; 295 | } 296 | } 297 | 298 | // Creates a BLAKE2b hashing context 299 | // Requires an output length between 1 and 64 bytes 300 | // Takes an optional Uint8Array key 301 | export function blake2bInit(outlen: u32, key: Uint8Array | null): Context { 302 | if (outlen === 0 || outlen > 64) { 303 | throw new Error("Illegal output length, expected 0 < length <= 64"); 304 | } 305 | if (key !== null && key.length > 64) { 306 | throw new Error("Illegal key, expected Uint8Array with 0 < length <= 64"); 307 | } 308 | 309 | // state, 'param block' 310 | const ctx = new Context(outlen); 311 | 312 | // initialize hash state 313 | for (let i = 0; i < 16; i++) { 314 | ctx.h[i] = BLAKE2B_IV32[i]; 315 | } 316 | const keylen: u64 = key !== null ? key.length : 0; 317 | ctx.h[0] ^= (0x01010000 ^ ((keylen as u64) << 8) ^ (outlen as u64)) as u32; 318 | 319 | // key the hash, if applicable 320 | if (key) { 321 | blake2bUpdate(ctx, key); 322 | // at the end 323 | ctx.c = 128; 324 | } 325 | 326 | return ctx; 327 | } 328 | 329 | // Updates a BLAKE2b streaming hash 330 | // Requires hash context and Uint8Array (byte array) 331 | export function blake2bUpdate(ctx: Context, input: Uint8Array): void { 332 | for (let i = 0; i < input.length; i++) { 333 | if (ctx.c === 128) { 334 | // buffer full ? 335 | ctx.t += ctx.c; // add counters 336 | blake2bCompress(ctx, false); // compress (not last) 337 | ctx.c = 0; // counter to zero 338 | } 339 | ctx.b[ctx.c++] = input[i]; 340 | } 341 | } 342 | 343 | // Completes a BLAKE2b streaming hash 344 | // Returns a Uint8Array containing the message digest 345 | function blake2bFinal(ctx: Context): Uint8Array { 346 | ctx.t += ctx.c; // mark last block offset 347 | 348 | while (ctx.c < 128) { 349 | // fill up with zeros 350 | ctx.b[ctx.c++] = 0; 351 | } 352 | blake2bCompress(ctx, true); // final block flag = 1 353 | // little endian convert and store 354 | const out = new Uint8Array(ctx.outlen); 355 | for (let i: u8 = 0; i < ctx.outlen; i++) { 356 | out[i] = ctx.h[i >> 2] >> (8 * (i & 3)); 357 | } 358 | return out; 359 | } 360 | 361 | // Computes the BLAKE2B hash of a string or byte array, and returns a Uint8Array 362 | // 363 | // Returns a n-byte Uint8Array 364 | // 365 | // Parameters: 366 | // - input - the input bytes, as a string, Buffer or Uint8Array 367 | // - key - optional key Uint8Array, up to 64 bytes 368 | // - outlen - optional output length in bytes, default 64 369 | export function blake2b( 370 | input: Uint8Array, 371 | key: Uint8Array | null = null, 372 | outlen: u32 = 64 373 | ): Uint8Array { 374 | // do the math 375 | const ctx = blake2bInit(outlen, key); 376 | blake2bUpdate(ctx, input); 377 | return blake2bFinal(ctx); 378 | } 379 | 380 | /** 381 | * FRIENDLY CAPTCHA optimization only, does not reset ctx.t (global byte counter) 382 | * Assumes no key 383 | */ 384 | export function blake2bResetForShortMessage(ctx: Context, input: Uint8Array): void { 385 | // Initialize State vector h with IV 386 | for (let i = 0; i < 16; i++) { 387 | ctx.h[i] = BLAKE2B_IV32[i]; 388 | } 389 | 390 | // Danger: These operations and resetting are really only possible because our input is exactly 128 bytes 391 | ctx.b.set(input); 392 | // ctx.m.fill(0); 393 | // ctx.v.fill(0); 394 | 395 | ctx.h[0] ^= 0x01010000 ^ (ctx.outlen as u32); 396 | } 397 | -------------------------------------------------------------------------------- /test/as-pect.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This function creates a test group in the test loader. 3 | * 4 | * @param {string} description - This is the name of the test group. 5 | * @param {() => void} callback - A function that contains all of the closures for this test group. 6 | * 7 | * @example 8 | * 9 | * ```ts 10 | * describe("my test suite", (): void => { 11 | * // put your tests here 12 | * }); 13 | * ``` 14 | */ 15 | declare function describe(description: string, callback: () => void): void; 16 | 17 | /** 18 | * This function creates a test inside the given test group. It must be placed inside a describe 19 | * block. 20 | * 21 | * @param {string} description - This is the name of the test, and should describe a behavior. 22 | * @param {() => void} callback - A function that contains a set of expectations for this test. 23 | * 24 | * @example 25 | * 26 | * ```ts 27 | * describe("the meaning of life", (): void => { 28 | * it("should be 42", (): void => { 29 | * // put your expectations here 30 | * expect(29 + 13).toBe(42); 31 | * }); 32 | * }); 33 | * ``` 34 | */ 35 | declare function it(description: string, callback: () => void): void; 36 | 37 | /** 38 | * A test that does not run, and is longhand equivalent to using todo function without a 39 | * callback. This test does not get run and is reported like a todo. 40 | * 41 | * @param {string} description - This is the name of the test, and should describe a behavior. 42 | * @param {() => void} callback - A function that contains a set of expectations for this test. 43 | */ 44 | declare function xit(description: string, callback: () => void): void; 45 | 46 | /** 47 | * A test that does not run, and is longhand equivalent to using todo function without a 48 | * callback. This test does not get run and is reported like a todo. 49 | * 50 | * @param {string} description - This is the name of the test, and should describe a behavior. 51 | * @param {() => void} callback - A function that contains a set of expectations for this test. 52 | */ 53 | declare function xtest(description: string, callback: () => void): void; 54 | 55 | /** 56 | * This function creates a test inside the given test group. It must be placed inside a describe 57 | * block. 58 | * 59 | * @param {string} description - This is the name of the test, and should describe a behavior. 60 | * @param {() => void} callback - A function that contains a set of expectations for this test. 61 | * 62 | * @example 63 | * ```ts 64 | * describe("the meaning of life", (): void => { 65 | * test("the value should be 42", (): void => { 66 | * // put your expectations here 67 | * expect(29 + 13).toBe(42); 68 | * }); 69 | * }); 70 | * ``` 71 | */ 72 | declare function test(description: string, callback: () => void): void; 73 | 74 | /** 75 | * This function creates a test that is expected to fail. This is useful to verify if a given 76 | * behavior is expected to throw. 77 | * 78 | * @param {string} description - This is the name of the test, and should describe a behavior. 79 | * @param {() => void} callback - A function that contains a set of expectations for this test. 80 | * @param {string?} message - A message that describes why the test should fail. 81 | * 82 | * @example 83 | * 84 | * ```ts 85 | * describe("the meaning of life", (): void => { 86 | * throws("the value should be 42", (): void => { 87 | * // put your expectations here 88 | * expect(29 + 13).not.toBe(42); 89 | * }); 90 | * }); 91 | * ``` 92 | */ 93 | declare function throws(description: string, callback: () => void, message?: string): void; 94 | 95 | /** 96 | * This function creates a test that is expected to fail. This is useful to verify if a given 97 | * behavior is expected to throw. 98 | * 99 | * @param {string} description - This is the name of the test, and should describe a behavior. 100 | * @param {() => void} callback - A function that contains a set of expectations for this test. 101 | * @param {string?} message - A message that describes why the test should fail. 102 | * 103 | * @example 104 | * 105 | * ```ts 106 | * describe("the meaning of life", (): void => { 107 | * itThrows("when the value should be 42", (): void => { 108 | * // put your expectations here 109 | * expect(29 + 13).not.toBe(42); 110 | * }, "The value is actually 42."); 111 | * }); 112 | * ``` 113 | */ 114 | declare function itThrows(description: string, callback: () => void, message?: string): void; 115 | 116 | /** 117 | * This function creates a callback that is called before each individual test is run in this test 118 | * group. 119 | * 120 | * @param {function} callback - The function to be run before each test in the current test group. 121 | * 122 | * @example 123 | * 124 | * ```ts 125 | * // create a global 126 | * var cat: Cat = new Cat(); 127 | * 128 | * describe("cats", (): void => { 129 | * beforeEach((): void => { 130 | * cat.meow(1); // meow once per test 131 | * }); 132 | * }); 133 | * ``` 134 | */ 135 | declare function beforeEach(callback: () => void): void; 136 | 137 | /** 138 | * This function creates a callback that is called before the whole test group is run, and only 139 | * once. 140 | * 141 | * @param {function} callback - The function to be run before each test in the current test group. 142 | * 143 | * @example 144 | * 145 | * ```ts 146 | * // create a global 147 | * var dog: Dog = null; 148 | * describe("dogs", (): void => { 149 | * beforeAll((): void => { 150 | * dog = new Dog(); // create a single dog once before the tests start 151 | * }); 152 | * }); 153 | * ``` 154 | */ 155 | declare function beforeAll(callback: () => void): void; 156 | 157 | /** 158 | * This function creates a callback that is called after each individual test is run in this test 159 | * group. 160 | * 161 | * @param {function} callback - The function to be run after each test in the current test group. 162 | * 163 | * @example 164 | * 165 | * ```ts 166 | * // create a global 167 | * var cat: Cat = new Cat(); 168 | * 169 | * describe("cats", (): void => { 170 | * afterEach((): void => { 171 | * cat.sleep(12); // cats sleep a lot 172 | * }); 173 | * }); 174 | * ``` 175 | */ 176 | declare function afterEach(callback: () => void): void; 177 | 178 | /** 179 | * This function creates a callback that is called after the whole test group is run, and only 180 | * once. 181 | * 182 | * @param {function} callback - The function to be run after each test in the current test group. 183 | * 184 | * @example 185 | * 186 | * ```ts 187 | * // create a global 188 | * var dog: Dog = null; 189 | * describe("dogs", (): void => { 190 | * afterAll((): void => { 191 | * memory.free(changetype(dog)); // free some memory 192 | * }); 193 | * }); 194 | * ``` 195 | */ 196 | declare function afterAll(callback: () => void): void; 197 | 198 | /** 199 | * Describes a value and returns an expectation to test the value. 200 | * 201 | * @type {T} - The expectation's type. 202 | * @param {T} actual - The value being tested. 203 | * 204 | * @example 205 | * 206 | * ```ts 207 | * expect(42).not.toBe(-1, "42 should not be -1"); 208 | * expect(19 + 23).toBe(42, "19 + 23 should equal 42"); 209 | * ``` 210 | */ 211 | declare function expect(actual: T | null): Expectation; 212 | 213 | /** 214 | * Describes a void function and returns an expectation to test the function. 215 | * 216 | * @param {() => void} callback - The callback being tested. 217 | * 218 | * @example 219 | * 220 | * ```ts 221 | * expectFn((): void => unreachable()).toThrow("unreachables do not throw"); 222 | * expectFn((): void => { 223 | * cat.meow(); 224 | * }).not.toThrow("Uhoh, cats can't meow!");; 225 | * ``` 226 | */ 227 | declare function expectFn(cb: () => void): Expectation<() => void>; 228 | 229 | /** 230 | * Describes a test that needs to be written. 231 | * 232 | * @param {string} description - The description of the test that needs to be written. 233 | */ 234 | declare function todo(description: string): void; 235 | 236 | /** 237 | * Logs a single value to the logger, and is stringified. It works for references, values, and 238 | * strings. 239 | * 240 | * @type {T} - The type to be logged. 241 | * @param {T | null} value - The value to be logged. 242 | * 243 | * @example 244 | * 245 | * ```ts 246 | * log("This is a logged value."); 247 | * log(42); 248 | * log(new Vec(1, 2, 3)); 249 | * log(null); 250 | * ``` 251 | */ 252 | declare function log(value: T | null): void; 253 | 254 | /** 255 | * An expectation for a value. 256 | */ 257 | // @ts-ignore 258 | declare class Expectation { 259 | /** 260 | * Create a new expectation. 261 | * 262 | * @param {T | null} actual - The actual value of the expectation. 263 | */ 264 | constructor(actual: T | null); 265 | 266 | /** 267 | * This expectation performs a strict equality on value types and reference types. 268 | * 269 | * @param {T | null} expected - The value to be compared. 270 | * @param {string} message - The optional message that describes the expectation. 271 | * 272 | * @example 273 | * 274 | * ```ts 275 | * expect(42).not.toBe(-1, "42 should not be -1"); 276 | * expect(19 + 23).toBe(42, "19 + 23 should equal 42"); 277 | * ``` 278 | */ 279 | toBe(expected: T | null, message?: string): void; 280 | 281 | /** 282 | * This expectation performs a strict equality on value types and performs a memcompare on 283 | * reference types. If the reference type `T` has reference types as properties, the comparison does 284 | * not perform property traversal. It will only compare the pointer values in the memory block, and 285 | * only compare `offsetof()` bytes, regardless of the allocated block size. 286 | * 287 | * @param {T | null} expected - The value to be compared. 288 | * @param {string} message - The optional message that describes the expectation. 289 | * 290 | * @example 291 | * 292 | * ```ts 293 | * expect(new Vec3(1, 2, 3)).toStrictEqual(new Vec(1, 2, 3), "Vectors of the same shape should be equal"); 294 | * ``` 295 | */ 296 | toStrictEqual(expected: T | null, message?: string): void; 297 | 298 | /** 299 | * This expectation performs a strict memory block equality based on the allocated block sizes. 300 | * 301 | * @param {T | null} expected - The value to be compared. 302 | * @param {string} message - The optional message that describes the expectation. 303 | * 304 | * @example 305 | * 306 | * ```ts 307 | * expect(new Vec3(1, 2, 3)).toBlockEqual(new Vec(1, 2, 3), "Vectors of the same shape should be equal"); 308 | * ``` 309 | */ 310 | toBlockEqual(expected: T | null, message?: string): void; 311 | 312 | /** 313 | * If the value is callable, it calls the function, and fails the expectation if it throws, or hits 314 | * an unreachable(). 315 | * 316 | * @param {string} message - The optional message that describes the expectation. 317 | * 318 | * @example 319 | * 320 | * ```ts 321 | * expectFn((): void => unreachable()).toThrow("unreachable() should throw."); 322 | * expectFn((): void => { 323 | * cat.sleep(100); // cats can sleep quite a lot 324 | * }).not.toThrow("cats should sleep, not throw"); 325 | * ``` 326 | */ 327 | toThrow(message?: string): void; 328 | 329 | /** 330 | * This expecation asserts that the value is truthy, like in javascript. If the value is a string, 331 | * then strings of length 0 are not truthy. 332 | * 333 | * @param {string} message - The optional message that describes the expectation. 334 | * 335 | * @example 336 | * 337 | * ```ts 338 | * expect(true).toBeTruthy("true is truthy."); 339 | * expect(1).toBeTruthy("numeric values that are not 0 are truthy."); 340 | * expect(new Vec3(1, 2, 3)).toBeTruthy("reference types that aren't null are truthy."); 341 | * expect(false).not.toBeTruthy("false is not truthy."); 342 | * expect(0).not.toBeTruthy("0 is not truthy."); 343 | * expect(null).not.toBeTruthy("null is not truthy."); 344 | * ``` 345 | */ 346 | toBeTruthy(message?: string): void; 347 | 348 | /** 349 | * This expectation tests the value to see if it is null. If the value is a value type, it is 350 | * never null. If the value is a reference type, it performs a strict null comparison. 351 | * 352 | * @param {string} message - The optional message that describes the expectation. 353 | * 354 | * @example 355 | * 356 | * ```ts 357 | * expect(0).not.toBeNull("numbers are never null"); 358 | * expect(null).toBeNull("null reference types are null."); 359 | * ``` 360 | */ 361 | toBeNull(message?: string): void; 362 | 363 | /** 364 | * This expecation assert that the value is falsy, like in javascript. If the value is a string, 365 | * then strings of length 0 are falsy. 366 | * 367 | * @param {string} message - The optional message that describes the expectation. 368 | * 369 | * @example 370 | * 371 | * ```ts 372 | * expect(false).toBeFalsy("false is falsy."); 373 | * expect(0).toBeFalsy("0 is falsy."); 374 | * expect(null).toBeFalsy("null is falsy."); 375 | * expect(true).not.toBeFalsy("true is not falsy."); 376 | * expect(1).not.toBeFalsy("numeric values that are not 0 are not falsy."); 377 | * expect(new Vec3(1, 2, 3)).not.toBeFalsy("reference types that aren't null are not falsy."); 378 | * ``` 379 | */ 380 | toBeFalsy(message?: string): void; 381 | 382 | /** 383 | * This expectation asserts that the value is greater than the expected value. Since operators can 384 | * be overloaded in assemblyscript, it's possible for this to work on reference types. 385 | * 386 | * @param {T | null} expected - The expected value that the actual value should be greater than. 387 | * @param {string} message - The optional message that describes this expectation. 388 | * 389 | * @example 390 | * 391 | * ```ts 392 | * expect(10).toBeGreaterThan(4); 393 | * expect(12).not.toBeGreaterThan(42); 394 | * ``` 395 | */ 396 | toBeGreaterThan(expected: T | null, message?: string): void; 397 | 398 | /** 399 | * This expectation asserts that the value is less than the expected value. Since operators can 400 | * be overloaded in assemblyscript, it's possible for this to work on reference types. 401 | * 402 | * @param {T | null} value - The expected value that the actual value should be less than. 403 | * @param {string} message - The optional message that describes this expectation. 404 | * 405 | * @example 406 | * 407 | * ```ts 408 | * expect(10).not.toBeLessThan(4); 409 | * expect(12).toBeLessThan(42); 410 | * ``` 411 | */ 412 | toBeLessThan(expected: T | null, message?: string): void; 413 | 414 | /** 415 | * This expectation asserts that the value is greater than or equal to the expected value. Since 416 | * operators can be overloaded in assemblyscript, it's possible for this to work on reference 417 | * types. 418 | * 419 | * @param {T | null} value - The expected value that the actual value should be greater than or 420 | * equal to. 421 | * @param {string} message - The optional message that describes this expectation. 422 | * 423 | * @example 424 | * 425 | * ```ts 426 | * expect(42).toBeGreaterThanOrEqual(42); 427 | * expect(10).toBeGreaterThanOrEqual(4); 428 | * expect(12).not.toBeGreaterThanOrEqual(42); 429 | * ``` 430 | */ 431 | toBeGreaterThanOrEqual(expected: T | null, message?: string): void; 432 | 433 | /** 434 | * This expectation asserts that the value is less than or equal to the expected value. Since 435 | * operators can be overloaded in assemblyscript, it's possible for this to work on reference 436 | * types. 437 | * 438 | * @param {T | null} value - The expected value that the actual value should be less than or equal 439 | * to. 440 | * @param {string} message - The optional message that describes this expectation. 441 | * 442 | * @example 443 | * 444 | * ```ts 445 | * expect(42).toBeLessThanOrEqual(42); 446 | * expect(10).not.toBeLessThanOrEqual(4); 447 | * expect(12).toBeLessThanOrEqual(42); 448 | * ``` 449 | */ 450 | toBeLessThanOrEqual(expected: T | null, message?: string): void; 451 | 452 | /** 453 | * This expectation asserts that the value is close to another value. Both numbers must be finite, 454 | * and T must extend f64 or f32. 455 | * 456 | * @param {T extends f64 | f32} value - The expected value to be close to. 457 | * @param {i32} decimalPlaces - The number of decimal places used to calculate epsilon. Default is 458 | * 2. 459 | * @param {string} message - The optional message that describes this expectation. 460 | * 461 | * @example 462 | * 463 | * ```ts 464 | * expect(0.1 + 0.2).toBeCloseTo(0.3); 465 | * ``` 466 | */ 467 | toBeCloseTo(expected: T, decimalPlaces?: number, message?: string): void; 468 | 469 | /** 470 | * This function asserts the float type value is NaN. 471 | * 472 | * @param {string} message - The optional message the describes this expectation. 473 | * 474 | * @example 475 | * 476 | * ```ts 477 | * expect(NaN).toBeNaN(); 478 | * expect(42).not.toBeNaN(); 479 | * ``` 480 | */ 481 | toBeNaN(message?: string): void; 482 | 483 | /** 484 | * This function asserts a float is finite. 485 | * 486 | * @param {string} message - The optional message the describes this expectation. 487 | * @example 488 | * 489 | * ```ts 490 | * expect(42).toBeFinite(); 491 | * expect(Infinity).not.toBeFinite(); 492 | * ``` 493 | */ 494 | toBeFinite(message?: string): void; 495 | 496 | /** 497 | * This method asserts the item has the expected length. 498 | * 499 | * @param {i32} expected - The expected length. 500 | * @param {string} message - The optional message the describes this expectation. 501 | * 502 | * ```ts 503 | * expect([1, 2, 3]).toHaveLength(3); 504 | * ``` 505 | */ 506 | toHaveLength(expected: i32, message?: string): void; 507 | 508 | /** 509 | * This method asserts that a given T that extends `Array` has a value/reference included. 510 | * 511 | * @param {valueof} expected - The expected item to be included in the Array. 512 | * @param {string} message - The optional message the describes this expectation. 513 | * 514 | * @example 515 | * 516 | * ```ts 517 | * expect([1, 2, 3]).toInclude(3); 518 | * ``` 519 | */ 520 | // @ts-ignore: expected value should be known at compile time 521 | toInclude | indexof>(expected: U, message?: string): void; 522 | 523 | /** 524 | * This method asserts that a given T that extends `Array` has a value/reference included. 525 | * 526 | * @param {valueof} expected - The expected item to be included in the Array. 527 | * @param {string} message - The optional message the describes this expectation. 528 | * 529 | * @example 530 | * 531 | * ```ts 532 | * expect([1, 2, 3]).toContain(3); 533 | * ``` 534 | */ 535 | // @ts-ignore: expected value should be known at compile time 536 | toContain(expected: valueof, message?: string): void; 537 | 538 | /** 539 | * This method asserts that a given T that extends `Array` has a value/reference included and 540 | * compared via memory.compare(). 541 | * 542 | * @param {i32} expected - The expected item to be included in the Array. 543 | * @param {string} message - The optional message the describes this expectation. 544 | * 545 | * @example 546 | * ```ts 547 | * expect([new Vec3(1, 2, 3)]).toInclude(new Vec3(1, 2, 3)); 548 | * ``` 549 | */ 550 | // @ts-ignore: expected value should be known at compile time 551 | toIncludeEqual | valueof>(expected: U, message?: string): void; 552 | 553 | /** 554 | * This method asserts that a given T that extends `Array` has a value/reference included and 555 | * compared via memory.compare(). 556 | * 557 | * @param {i32} expected - The expected item to be included in the Array. 558 | * @param {string} message - The optional message the describes this expectation. 559 | * 560 | * @example 561 | * ```ts 562 | * expect([new Vec3(1, 2, 3)]).toInclude(new Vec3(1, 2, 3)); 563 | * ``` 564 | */ 565 | // @ts-ignore: expected value should be known at compile time 566 | toContainEqual | valueof>(expected: U, message?: string): void; 567 | 568 | /** 569 | * Match a snapshot with a given name for this test. 570 | * 571 | * @param {string | null} name - The snapshot name. 572 | */ 573 | toMatchSnapshot(name?: string | null): void; 574 | 575 | /** 576 | * This computed property is chainable, and negates the existing expectation. It returns itself. 577 | * 578 | * @example 579 | * ```ts 580 | * expect(42).not.toBe(0, "42 is not 0"); 581 | */ 582 | not: Expectation; 583 | 584 | /** 585 | * The actual value of the expectation. 586 | */ 587 | actual: T | null; 588 | } 589 | 590 | /** 591 | * This is called to stop the debugger. e.g. `node --inspect-brk asp`. 592 | */ 593 | declare function debug(): void; 594 | 595 | /** 596 | * Assemblyscript uses reference counting to perform garbage collection. This means when you 597 | * allocate a managed object and return it, it's reference count is one. If another variable aliases 598 | * it then the reference count goes up. This static class contains a few convenience methods for 599 | * developers to test the current number of blocks allocated on the heap to make sure you aren't leaking 600 | * references, e.i. keeping references to objects you expect to be collected. 601 | */ 602 | declare class RTrace { 603 | /** 604 | * This bool indicates if `RTrace` should call into JavaScript to obtain reference counts. 605 | */ 606 | public static enabled: bool; 607 | 608 | /** 609 | * This method returns the current number of active references on the heap. 610 | */ 611 | public static count(): i32; 612 | 613 | /** 614 | * This method starts a new refcounting group, and causes the next call to `RTrace.end(label)` to 615 | * return a delta in reference counts on the heap. 616 | * 617 | * @param {i32} label - The numeric label for this refcounting group. 618 | */ 619 | public static start(label: i32): void; 620 | 621 | /** 622 | * This method returns a delta of how many new (positive) or collected (negative) are on the heap. 623 | * 624 | * @param {i32} label - The numeric label for this refcounting group. 625 | */ 626 | public static end(label: i32): i32; 627 | 628 | /** 629 | * This method returns the number of increments that have occurred over the course of a test 630 | * file. 631 | */ 632 | public static increments(): i32; 633 | 634 | /** 635 | * This method returns the number of decrements that have occurred over the course of a test 636 | * file. 637 | */ 638 | public static decrements(): i32; 639 | 640 | /** 641 | * This method returns the number of increments that have occurred over the course of a test 642 | * node. 643 | */ 644 | public static nodeIncrements(): i32; 645 | 646 | /** 647 | * This method returns the number of decrements that have occurred over the course of a test 648 | * node. 649 | */ 650 | public static nodeDecrements(): i32; 651 | 652 | /** 653 | * This method returns the number of allocations that have occurred over the course of a test 654 | * file. 655 | */ 656 | public static allocations(): i32; 657 | 658 | /** 659 | * This method returns the number of frees that have occurred over the course of a test 660 | * file. 661 | */ 662 | public static frees(): i32; 663 | 664 | /** 665 | * This method returns the number of allocations that have occurred over the course of a test 666 | * node. 667 | */ 668 | public static nodeAllocations(): i32; 669 | 670 | /** 671 | * This method returns the number of frees that have occurred over the course of a test 672 | * node. 673 | */ 674 | public static nodeFrees(): i32; 675 | 676 | /** 677 | * Gets the current number of reallocations over the course of the TestContext module. 678 | */ 679 | public static reallocations(): i32; 680 | 681 | /** 682 | * Gets the current number of reallocations over the course of the test node. 683 | */ 684 | public static nodeReallocations(): i32; 685 | 686 | /** 687 | * This method triggers a garbage collection. 688 | */ 689 | public static collect(): void; 690 | 691 | /** 692 | * Get the type id (class id) of the pointer. 693 | * 694 | * @param {usize} pointer - The pointer. 695 | * @returns {u32} - The type id of the allocated block. 696 | */ 697 | public static typeIdOf(pointer: usize): u32; 698 | 699 | /** 700 | * Get the type id (class id) of a reference. 701 | * 702 | * @param {T} reference - The reference. 703 | * @returns {u32} - The type id of the allocated block. 704 | */ 705 | public static typeIdOfReference(reference: T): u32; 706 | 707 | /** 708 | * Get the size of a pointer. 709 | * 710 | * @param {usize} pointer - The pointer. 711 | * @returns {u32} - The size of the allocated block. 712 | */ 713 | public static sizeOf(pointer: usize): u32; 714 | 715 | /** 716 | * Get the size of a reference. 717 | * 718 | * @param {T} reference - The reference. 719 | * @returns {u32} - The size of the allocated block. 720 | */ 721 | public static sizeOfReference(reference: T): u32; 722 | 723 | /** 724 | * Get the currently allocated blocks. 725 | */ 726 | public static activeBlocks(): usize[]; 727 | 728 | /** 729 | * Get the current groups allocated blocks. 730 | */ 731 | public static activeGroupBlocks(): usize[]; 732 | 733 | /** 734 | * Get the current tests allocated blocks. 735 | */ 736 | public static activeTestBlocks(): usize[]; 737 | 738 | /** 739 | * Get the arc reference count of a given pointer to a managed reference. 740 | * 741 | * @param {usize} ptr - The pointer to the managed reference. 742 | */ 743 | public static refCountOf(ptr: usize): u32; 744 | 745 | /** 746 | * Gets the current count of the specified reference. 747 | * @param {T} reference - the reference. 748 | */ 749 | public static refCountOfReference(reference: T): u32; 750 | } 751 | 752 | /** 753 | * This class is static and contains private global values that contain metadata about the Actual 754 | * value. 755 | * 756 | * @example 757 | * ```ts 758 | * Actual.report("This is an expected string."); 759 | * Actual.report([1, 2, 3]); 760 | * Actual.report(42); 761 | * ``` 762 | */ 763 | declare class Actual { 764 | /** 765 | * This function performs reporting to javascript what the actual value of this expectation is. 766 | * 767 | * @param {T} actual - The actual value to be reported. 768 | */ 769 | public static report(value: T): void; 770 | 771 | /** 772 | * Clear the actual value and release any private memory stored as a global. 773 | */ 774 | public static clear(): void; 775 | } 776 | 777 | /** 778 | * This class is static and contains private global values that contain metadata about the Expected 779 | * value. 780 | * 781 | * @example 782 | * ```ts 783 | * Expected.report("This is an expected string."); 784 | * Expected.report([1, 2, 3]); 785 | * Expected.report(42, i32(true)); // not 42 786 | * ``` 787 | */ 788 | declare class Expected { 789 | /** 790 | * This function performs reporting to javascript what the expected value of this expectation is. 791 | * It notifies javascript if the expectation is negated. 792 | * 793 | * @param {T} value - The actual value to be reported. 794 | * @param {i32} negated - An indicator if the expectation is negated. Pass `1` to negate the 795 | * expectation. (default: 0) 796 | */ 797 | public static report(value: T, negated?: i32): void; 798 | 799 | /** 800 | * Report an expected truthy value to the host, and if the expectation is negated. 801 | * 802 | * @param {i32} negated - A value, 1 or 0 indicating if the expectation is negated. 803 | */ 804 | static reportTruthy(negated?: i32): void; 805 | 806 | /** 807 | * Report an expected falsy value to the host, and if the expectation is negated. 808 | * 809 | * @param {i32} negated - A value, 1 or 0 indicating if the expectation is negated. 810 | */ 811 | static reportFalsy(negated?: i32): void; 812 | 813 | /** 814 | * Report an expected finite value to the host, and if the expectation is negated. 815 | * 816 | * @param {i32} negated - A value, 1 or 0 indicating if the expectation is negated. 817 | */ 818 | static reportFinite(negated?: i32): void; 819 | 820 | /** 821 | * Report a snapshot of type T with a given name. 822 | * 823 | * @param {T} actual - The actual value. 824 | * @param {string} name - The snapshot name. 825 | */ 826 | static reportSnapshot(actual: T, name?: string | null): void; 827 | 828 | /** 829 | * Clear the expected value and release any private memory stored as a global. 830 | */ 831 | public static clear(): void; 832 | } 833 | 834 | /** 835 | * Reflection namespace for comparing references of a specific type. 836 | */ 837 | declare class Reflect { 838 | /** A successful matching indicator. */ 839 | public static SUCCESSFUL_MATCH: i32; 840 | /** An indicator that a matching operation has failed. */ 841 | public static FAILED_MATCH: i32; 842 | /** A const to define when a matching operation should wait because a circular reference is currently resolving a match. */ 843 | public static DEFER_MATCH: i32; 844 | 845 | /** 846 | * Create a reflected value for inspection. 847 | * 848 | * @param {T} value - The value to be inspected. 849 | * @param {Map?} seen - A map of pointers to hostValues for caching purposes. 850 | */ 851 | public static toReflectedValue(value: T, seen?: Map): i32; 852 | /** 853 | * A method used for comparing two values or references to determine if they match each other. 854 | * 855 | * @param {T} left - One of the values being compared. 856 | * @param {T} right - One of the values being compared. 857 | * @param {usize[]} stack - Internal use only, used to prevent recursion. 858 | * @param {usize[]} cache - Internal use only, used to prevent recursion. 859 | */ 860 | public static equals(left: T, right: T, stack?: usize[], cache?: usize[]): i32; 861 | 862 | /** 863 | * Attach a stack trace to a value. 864 | * 865 | * @param {i32} id - The reflected value to attach the current stack trace to. 866 | */ 867 | public static attachStackTrace(id: i32): void; 868 | } 869 | --------------------------------------------------------------------------------