├── .babelrc ├── .eslintrc.json ├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── jest.config.ts ├── package.json ├── postcss.config.cjs ├── src ├── GPUFunctionLoader.tsx ├── aleo-wasm-loader │ ├── aleo_wasm.d.ts │ ├── aleo_wasm.js │ ├── aleo_wasm_bg.js │ ├── aleo_wasm_bg.wasm │ ├── aleo_wasm_bg.wasm.d.ts │ └── wasm-loader.ts ├── algorithms │ ├── ExponentiationBySquaring.test.ts │ ├── ExponentiationBySquaring.ts │ ├── Kochanski.test.ts │ ├── Kochanski.ts │ ├── ShanksTonelli.test.ts │ └── ShanksTonelli.ts ├── barretenberg-wasm-loader │ └── wasm-functions.ts ├── gpu │ ├── U32Sizes.ts │ ├── curveSpecific.ts │ ├── entries │ │ ├── bls12-377Algorithms │ │ │ ├── aleoIsOwnerEntry.ts │ │ │ ├── aleoIsOwnerMultipassEntry.ts │ │ │ ├── aleoPoseidonEntry.ts │ │ │ └── aleoPoseidonMultiPass.ts │ │ ├── curve │ │ │ ├── curveAddPoints.test.ts │ │ │ ├── curveAddPointsEntry.ts │ │ │ ├── curveDoublePoint.test.ts │ │ │ ├── curveDoublePointEntry.ts │ │ │ ├── curveMulPoint.test.ts │ │ │ ├── curveMulPointEntry.ts │ │ │ ├── curveMulPointMultiPassEntry.ts │ │ │ ├── curveMulPointWindowed.test.ts │ │ │ └── curveMulPointWindowedEntry.ts │ │ ├── entryCreator.ts │ │ ├── field │ │ │ ├── evalString.ts │ │ │ ├── fieldAdd.test.ts │ │ │ ├── fieldEntry.ts │ │ │ ├── fieldModulusExponentiation.test.ts │ │ │ ├── fieldModulusFieldInverse.test.ts │ │ │ ├── fieldModulusFieldMultiply.test.ts │ │ │ ├── fieldModulusFieldReduce.test.ts │ │ │ ├── fieldSqrt.test.ts │ │ │ └── fieldSub.test.ts │ │ ├── isOwnerMultipassReuseBuffers.ts │ │ ├── multipassEntryCreator.ts │ │ ├── multipassEntryCreatorBufferReuse.ts │ │ ├── naiveMSMEntry.ts │ │ ├── ntt │ │ │ └── nttMultipassEntry.ts │ │ ├── pippengerMSMEntry.test.ts │ │ ├── pippengerMSMEntry.ts │ │ ├── pointScalarMultipassReuseBuffer.ts │ │ ├── poseidonMultiPassBufferReuse.ts │ │ └── u256 │ │ │ ├── evalString.ts │ │ │ ├── u256Add.test.ts │ │ │ ├── u256Double.test.ts │ │ │ ├── u256Entry.ts │ │ │ ├── u256GT.test.ts │ │ │ ├── u256GTEntry.ts │ │ │ ├── u256RightShift.test.ts │ │ │ ├── u256RightShiftEntry.ts │ │ │ ├── u256Sub.test.ts │ │ │ └── u256SubW.test.ts │ ├── prune.ts │ ├── utils.test.ts │ ├── utils.ts │ └── wgsl │ │ ├── AleoPoseidon.ts │ │ ├── AleoPoseidonConstants.ts │ │ ├── BLS12-377CurveBaseWGSL.ts │ │ ├── BLS12-377Params.ts │ │ ├── BN254CurveBaseWGSL.ts │ │ ├── BN254Params.ts │ │ ├── Curve.ts │ │ ├── FieldModulus.ts │ │ └── U256.ts ├── index.html ├── index.tsx ├── main.css ├── params │ ├── AleoPoseidonParams.ts │ ├── BLS12_377Constants.ts │ └── BN254Constants.ts ├── parsers │ └── aleo │ │ ├── AddressParser.test.ts │ │ ├── AddressParser.ts │ │ ├── RecordParser.test.ts │ │ └── RecordParser.ts ├── ui │ ├── Accordion.tsx │ ├── AllBenchmarks.tsx │ ├── Benchmark.tsx │ ├── CSVExportButton.tsx │ ├── NTTBenchmark.tsx │ └── PippengerBenchmark.tsx └── utils │ ├── BLS12_377FieldMath.test.ts │ ├── BLS12_377FieldMath.ts │ ├── aleoWasmFunctions.test.ts │ ├── aleoWasmFunctions.ts │ └── inputsGenerator.ts ├── tailwind.config.js ├── tsconfig.json ├── webpack.dev.config.cjs ├── webpack.prod.config.cjs └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "esmodules": true 8 | } 9 | } 10 | ], 11 | "@babel/preset-react", 12 | "@babel/preset-typescript" 13 | ], 14 | "plugins": [ 15 | [ 16 | "@babel/plugin-transform-runtime", 17 | { 18 | "regenerator": true 19 | } 20 | ] 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 2018, 5 | "sourceType": "module" 6 | }, 7 | "plugins": ["@typescript-eslint", "react-hooks"], 8 | "extends": [ 9 | "plugin:react/recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "ignorePatterns": ["node_modules", "dist", "aleo_wasm**", "**/barretenberg-wasm-loader"], 13 | "rules": { 14 | "react-hooks/rules-of-hooks": "error", 15 | "react-hooks/exhaustive-deps": "warn", 16 | "react/prop-types": "off", 17 | "@typescript-eslint/no-non-null-assertion": "off" 18 | }, 19 | "settings": { 20 | "react": { 21 | "pragma": "React", 22 | "version": "detect" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug Jest Tests", 9 | "type": "node", 10 | "request": "launch", 11 | "runtimeArgs": ["--experimental-vm-modules", "--inspect-brk", "${workspaceRoot}/node_modules/.bin/jest", "--runInBand", "--testPathIgnorePatterns='/barretenberg-wasm-loader/|/gpu/'"], 12 | "console": "integratedTerminal", 13 | "internalConsoleOptions": "neverOpen" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Demox Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | This repository is result of a joint effort of [Demox Labs](https://www.demoxlabs.xyz/), [Aleo](https://aleo.org/) and [Aztec](https://aztec.network/) to accelerate Zero Knowledge cryptography in the browser using WebGPU. 4 | 5 | Right now, most operations are supported on BLS12-377 & BN-254. 6 | 7 | Support operations include: 8 | * Field Math 9 | * Curve Math 10 | * Multi-Scalar Multiplications (MSMs) 11 | * Poseidon Hashes 12 | * Number Theoretic Transforms (NTTs aka FFTs) 13 | 14 | 15 | ## Quick Start 16 | 17 | Ensure you have: 18 | 19 | - [Node.js](https://nodejs.org) 16 or later installed 20 | - [Yarn](https://yarnpkg.com) v1 or v2 installed 21 | 22 | Then run the following: 23 | 24 | ### 1) Clone the repository 25 | 26 | ```bash 27 | git clone https://github.com/demox-labs/webgpu-crypto && cd webgpu-crypto 28 | ``` 29 | 30 | ### 2) Install dependencies 31 | 32 | ```bash 33 | yarn 34 | ``` 35 | 36 | ### 3) Development 37 | 38 | Run a local server on localhost:4040. 39 | 40 | ```bash 41 | yarn start 42 | ``` 43 | Note -- running webgpu functions will only work on [browsers compatible](https://caniuse.com/webgpu) with webgpu. 44 | 45 | ## Preview of Benchmarks 46 | 47 | benchmarks-preview-1 48 | benchmarks-preview-2 49 | 50 | 51 | ## Trouble Shooting 52 | 53 | Common issues: 54 | * If you are unable to run the webgpu benchmarks, ensure you are using a webgpu-compatible browser. 55 | * If you are not able to load the test case data, be sure you have installed git LFS. You can either reclone the repo after installing git LFS or run `git lfs fetch && git lfs pull`. 56 | * If you run into general npm package errors, make sure you have nodejs v16 or later installed. 57 | * If you are using webgpu functions and getting all 0s as output, you may have hit an out of memory error in the gpu. Reduce your input size or consider breaking your computions into smaller steps. 58 | 59 | ## Disclaimer 60 | 61 | None of this code has been audited for correctness or security. Use at your own risk. 62 | 63 | We will additionally be releasing an npm package to make using this code easier. 64 | 65 | ## Reference 66 | 67 | [1] Scalar-multiplication algorithms. [https://cryptojedi.org/peter/data/eccss-20130911b.pdf](https://cryptojedi.org/peter/data/eccss-20130911b.pdf) 68 | 69 | [2] wgsl reference [https://www.w3.org/TR/WGSL/](https://www.w3.org/TR/WGSL/) 70 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | // All imported modules in your tests should be mocked automatically 8 | // automock: false, 9 | 10 | // Stop running tests after `n` failures 11 | // bail: 0, 12 | 13 | // The directory where Jest should store its cached dependency information 14 | // cacheDirectory: "/private/var/folders/d9/nj22nzln2gz_skvf938hhd0m0000gn/T/jest_dx", 15 | 16 | // Automatically clear mock calls, instances, contexts and results before every test 17 | clearMocks: true, 18 | 19 | // Indicates whether the coverage information should be collected while executing the test 20 | collectCoverage: false, 21 | 22 | // An array of glob patterns indicating a set of files for which coverage information should be collected 23 | // collectCoverageFrom: undefined, 24 | 25 | // The directory where Jest should output its coverage files 26 | coverageDirectory: "coverage", 27 | 28 | // Amount of time (in milliseconds) to wait for hook in test 29 | testTimeout: 60000, 30 | 31 | // An array of regexp pattern strings used to skip coverage collection 32 | // coveragePathIgnorePatterns: [ 33 | // "/node_modules/" 34 | // ], 35 | 36 | // Indicates which provider should be used to instrument code for coverage 37 | // coverageProvider: "babel", 38 | 39 | // A list of reporter names that Jest uses when writing coverage reports 40 | // coverageReporters: [ 41 | // "json", 42 | // "text", 43 | // "lcov", 44 | // "clover" 45 | // ], 46 | 47 | // An object that configures minimum threshold enforcement for coverage results 48 | // coverageThreshold: undefined, 49 | 50 | // A path to a custom dependency extractor 51 | // dependencyExtractor: undefined, 52 | 53 | // Make calling deprecated APIs throw helpful error messages 54 | // errorOnDeprecated: false, 55 | 56 | // The default configuration for fake timers 57 | // fakeTimers: { 58 | // "enableGlobally": false 59 | // }, 60 | 61 | // Force coverage collection from ignored files using an array of glob patterns 62 | // forceCoverageMatch: [], 63 | 64 | // A path to a module which exports an async function that is triggered once before all test suites 65 | // globalSetup: undefined, 66 | 67 | // A path to a module which exports an async function that is triggered once after all test suites 68 | // globalTeardown: undefined, 69 | 70 | // A set of global variables that need to be available in all test environments 71 | // globals: {}, 72 | 73 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 74 | // maxWorkers: "50%", 75 | 76 | // An array of directory names to be searched recursively up from the requiring module's location 77 | // moduleDirectories: [ 78 | // "node_modules" 79 | // ], 80 | 81 | // An array of file extensions your modules use 82 | // moduleFileExtensions: [ 83 | // "js", 84 | // "mjs", 85 | // "cjs", 86 | // "jsx", 87 | // "ts", 88 | // "tsx", 89 | // "json", 90 | // "node" 91 | // ], 92 | 93 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 94 | // moduleNameMapper: {}, 95 | 96 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 97 | // modulePathIgnorePatterns: [], 98 | 99 | // Activates notifications for test results 100 | // notify: false, 101 | 102 | // An enum that specifies notification mode. Requires { notify: true } 103 | // notifyMode: "failure-change", 104 | 105 | // A preset that is used as a base for Jest's configuration 106 | // preset: undefined, 107 | 108 | // Run tests from one or more projects 109 | // projects: undefined, 110 | 111 | // Use this configuration option to add custom reporters to Jest 112 | // reporters: undefined, 113 | 114 | // Automatically reset mock state before every test 115 | // resetMocks: false, 116 | 117 | // Reset the module registry before running each individual test 118 | // resetModules: false, 119 | 120 | // A path to a custom resolver 121 | // resolver: undefined, 122 | 123 | // Automatically restore mock state and implementation before every test 124 | // restoreMocks: false, 125 | 126 | // The root directory that Jest should scan for tests and modules within 127 | // rootDir: undefined, 128 | 129 | // A list of paths to directories that Jest should use to search for files in 130 | // roots: [ 131 | // "" 132 | // ], 133 | 134 | // Allows you to use a custom runner instead of Jest's default test runner 135 | // runner: "jest-runner", 136 | 137 | // The paths to modules that run some code to configure or set up the testing environment before each test 138 | // setupFiles: [], 139 | 140 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 141 | // setupFilesAfterEnv: [], 142 | 143 | // The number of seconds after which a test is considered as slow and reported as such in the results. 144 | // slowTestThreshold: 5, 145 | 146 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 147 | // snapshotSerializers: [], 148 | 149 | // The test environment that will be used for testing 150 | // testEnvironment: "jest-environment-node", 151 | 152 | // Options that will be passed to the testEnvironment 153 | // testEnvironmentOptions: {}, 154 | 155 | // Adds a location field to test results 156 | // testLocationInResults: false, 157 | 158 | // The glob patterns Jest uses to detect test files 159 | // testMatch: [ 160 | // "**/__tests__/**/*.[jt]s?(x)", 161 | // "**/?(*.)+(spec|test).[tj]s?(x)" 162 | // ], 163 | 164 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 165 | testPathIgnorePatterns: [ 166 | "/node_modules/", 167 | "/barretenberg-wasm-loader/" 168 | ], 169 | 170 | // The regexp pattern or array of patterns that Jest uses to detect test files 171 | // testRegex: [], 172 | 173 | // This option allows the use of a custom results processor 174 | // testResultsProcessor: undefined, 175 | 176 | // This option allows use of a custom test runner 177 | // testRunner: "jest-circus/runner", 178 | 179 | // A map from regular expressions to paths to transformers 180 | // transform: undefined, 181 | 182 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 183 | transformIgnorePatterns: [ 184 | "/node_modules/", 185 | "\\.pnp\\.[^\\/]+$" 186 | ], 187 | 188 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 189 | // unmockedModulePathPatterns: undefined, 190 | 191 | // Indicates whether each individual test should be reported during the run 192 | // verbose: undefined, 193 | 194 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 195 | // watchPathIgnorePatterns: [], 196 | 197 | // Whether to use watchman for file crawling 198 | // watchman: true, 199 | }; 200 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.0.1", 4 | "type": "module", 5 | "scripts": { 6 | "start": "webpack serve --config webpack.dev.config.cjs", 7 | "start-prod": "webpack serve --config webpack.prod.config.cjs", 8 | "lint": "eslint --ext .js,.ts .", 9 | "build": "webpack --config webpack.dev.config.cjs", 10 | "test": "node --experimental-vm-modules $(yarn bin jest) --testPathIgnorePatterns='/barretenberg-wasm-loader/'", 11 | "test-no-gpu": "node --experimental-vm-modules $(yarn bin jest) --testPathIgnorePatterns='/barretenberg-wasm-loader/|/gpu/'", 12 | "test-specific": "node --experimental-vm-modules $(yarn bin jest) --testPathPattern gpu/utils", 13 | "whosinmyswamp": "sudo lsof -i :4000" 14 | }, 15 | "dependencies": { 16 | "@demox-labs/aztec-bb.js": "^0.12.0", 17 | "@noble/curves": "^1.9.0", 18 | "@webgpu/types": "^0.1.30", 19 | "bech32": "^2.0.0", 20 | "bn.js": "^5.2.1", 21 | "bs58": "^5.0.0", 22 | "buffer": "^6.0.3", 23 | "comlink": "^4.4.1", 24 | "crypto-browserify": "^3.12.0", 25 | "html-webpack-plugin": "^5.5.0", 26 | "idb-keyval": "^6.2.1", 27 | "path-browserify": "^1.0.1", 28 | "react": "^17.0.1", 29 | "react-dom": "^17.0.1", 30 | "save-dev": "0.0.1-security", 31 | "stream-browserify": "^3.0.0" 32 | }, 33 | "devDependencies": { 34 | "@babel/core": "^7.17.5", 35 | "@babel/plugin-transform-runtime": "^7.17.0", 36 | "@babel/preset-env": "^7.16.11", 37 | "@babel/preset-react": "^7.16.7", 38 | "@babel/preset-typescript": "^7.16.7", 39 | "@babel/runtime": "^7.17.2", 40 | "@types/bn.js": "^5.1.1", 41 | "@types/fork-ts-checker-webpack-plugin": "^0.4.5", 42 | "@types/jest": "^29.5.0", 43 | "@types/react": "^17.0.40", 44 | "@types/react-dom": "^17.0.13", 45 | "@types/webpack-dev-server": "^4.7.2", 46 | "@typescript-eslint/eslint-plugin": "^5.14.0", 47 | "@typescript-eslint/parser": "^5.14.0", 48 | "autoprefixer": "^10.4.14", 49 | "babel-loader": "^8.2.3", 50 | "clean-webpack-plugin": "^4.0.0", 51 | "css-loader": "^6.7.3", 52 | "eslint": "^8.11.0", 53 | "eslint-plugin-react": "^7.29.3", 54 | "eslint-plugin-react-hooks": "^4.3.0", 55 | "eslint-webpack-plugin": "^2.4.1", 56 | "fork-ts-checker-webpack-plugin": "^7.2.1", 57 | "jest": "^29.5.0", 58 | "jest-environment-puppeteer": "^8.0.6", 59 | "jest-puppeteer": "^8.0.6", 60 | "postcss": "^8.4.23", 61 | "postcss-loader": "^7.3.0", 62 | "postcss-preset-env": "^8.3.2", 63 | "postcss-scss": "^4.0.6", 64 | "puppeteer": "^19.9.0", 65 | "style-loader": "^3.3.2", 66 | "tailwindcss": "^3.3.2", 67 | "ts-jest": "^29.1.0", 68 | "ts-node": "^10.9.1", 69 | "typescript": "^5.8.3", 70 | "webpack": "^5.70.0", 71 | "webpack-cli": "^4.9.2", 72 | "webpack-dev-server": "^4.7.4" 73 | }, 74 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 75 | } 76 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ident: 'postcss', 3 | syntax: 'postcss-scss', 4 | parser: 'postcss-scss', 5 | plugins: [require('postcss-preset-env'), require('tailwindcss'), require('autoprefixer')] 6 | }; 7 | -------------------------------------------------------------------------------- /src/GPUFunctionLoader.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import React from "react"; 3 | import { u256_right_shift } from "./gpu/entries/u256/u256RightShiftEntry"; 4 | import { point_add } from "./gpu/entries/curve/curveAddPointsEntry"; 5 | import { point_double } from "./gpu/entries/curve/curveDoublePointEntry"; 6 | import { point_mul } from "./gpu/entries/curve/curveMulPointEntry"; 7 | import { field_entry } from "./gpu/entries/field/fieldEntry"; 8 | import { CurveType } from "./gpu/curveSpecific"; 9 | import { u256_entry } from "./gpu/entries/u256/u256Entry"; 10 | import { u256_gt } from "./gpu/entries/u256/u256GTEntry"; 11 | import { gpuU32Inputs } from "./gpu/utils"; 12 | 13 | const GPUFunctionLoader: React.FC = () => { 14 | const input: gpuU32Inputs = { u32Inputs: new Uint32Array(), individualInputSize: 0}; 15 | // used for testing -- without this, the functions are not loaded onto the dom and cannot be called in-browser through puppeteer. 16 | const double_input_functions = [u256_gt, point_add, point_mul]; 17 | const single_input_functions = [point_double]; 18 | const single_u256s_input_with_constant = [u256_right_shift] 19 | return ( 20 |
21 | {double_input_functions.map((fn, index) => ( 22 | 25 | ))} 26 | {single_input_functions.map((fn, index) => ( 27 | 30 | ))} 31 | {single_u256s_input_with_constant.map((fn, index) => ( 32 | 35 | ))} 36 |
39 | ); 40 | }; 41 | 42 | export default GPUFunctionLoader; -------------------------------------------------------------------------------- /src/aleo-wasm-loader/aleo_wasm.js: -------------------------------------------------------------------------------- 1 | import * as wasm from "./aleo_wasm_bg.wasm"; 2 | import { __wbg_set_wasm } from "./aleo_wasm_bg.js"; 3 | __wbg_set_wasm(wasm); 4 | export * from "./aleo_wasm_bg.js"; 5 | -------------------------------------------------------------------------------- /src/aleo-wasm-loader/aleo_wasm_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demox-labs/webgpu-crypto/760ce3b0b413039a3f65ad3d007a8e2896925300/src/aleo-wasm-loader/aleo_wasm_bg.wasm -------------------------------------------------------------------------------- /src/aleo-wasm-loader/wasm-loader.ts: -------------------------------------------------------------------------------- 1 | export async function loadWasmModule() { 2 | const Aleo = await import('./aleo_wasm.js'); 3 | return Aleo; 4 | } -------------------------------------------------------------------------------- /src/algorithms/ExponentiationBySquaring.test.ts: -------------------------------------------------------------------------------- 1 | import { Exponentiation } from './ExponentiationBySquaring'; 2 | 3 | describe('Exponentiation', () => { 4 | it.each([ 5 | [BigInt(2), BigInt(3), BigInt(50), BigInt(8)] 6 | ])('properly does modulus multiplication', (a: bigint, b: bigint, modulus: bigint, expected: bigint) => { 7 | const result = Exponentiation(a, b, modulus); 8 | expect(result).toEqual(expected); 9 | }); 10 | }); -------------------------------------------------------------------------------- /src/algorithms/ExponentiationBySquaring.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_MODULUS } from "../params/BLS12_377Constants"; 2 | 3 | export const Exponentiation = function exponentiation(base: bigint, exp: bigint, mod: bigint = FIELD_MODULUS) { 4 | base %= mod; // Update base if it is more than or equal to N 5 | let result = BigInt(1); 6 | 7 | while (exp > 0) { 8 | // If exp is odd, multiply base with result 9 | if (exp % BigInt(2) == BigInt(1)) { 10 | result = (result * base) % mod; 11 | } 12 | 13 | // exp must be even now, so we can safely divide it by 2 14 | exp = BigInt(exp) / BigInt(2); 15 | base = (base * base) % mod; 16 | } 17 | 18 | return result; 19 | } -------------------------------------------------------------------------------- /src/algorithms/Kochanski.test.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_MODULUS } from '../params/BLS12_377Constants'; 2 | import { Kochanski } from './Kochanski'; 3 | 4 | describe('Kochanski', () => { 5 | it.each([ 6 | [BigInt(8), BigInt(7), BigInt(50), BigInt(6)], 7 | [BigInt(8), BigInt(7), BigInt(100), BigInt(56)], 8 | [BigInt(123456789), BigInt(987654321), BigInt(1000000007), BigInt(259106859)], 9 | [BigInt(100), BigInt(100), BigInt(100), BigInt(0)], 10 | [FIELD_MODULUS, BigInt(0), FIELD_MODULUS, BigInt(0)], 11 | [FIELD_MODULUS, BigInt(2), FIELD_MODULUS, BigInt(0)], 12 | [FIELD_MODULUS, FIELD_MODULUS, FIELD_MODULUS, BigInt(0)], 13 | [FIELD_MODULUS + BigInt(2), BigInt(2), FIELD_MODULUS, BigInt(4)], 14 | ])('properly does modulus multiplication', (a: bigint, b: bigint, modulus: bigint, expected: bigint) => { 15 | const result = Kochanski(a, b, modulus); 16 | expect(result).toEqual(expected); 17 | }); 18 | }); -------------------------------------------------------------------------------- /src/algorithms/Kochanski.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_MODULUS } from "../params/BLS12_377Constants"; 2 | 3 | export const Kochanski = (a: bigint, b: bigint, modulus: bigint = FIELD_MODULUS) => { 4 | let accumulator = BigInt(0); 5 | 6 | while (b > 0) { 7 | if (b % BigInt(2) === BigInt(1)) { 8 | accumulator += a; 9 | if (accumulator >= modulus) { 10 | accumulator -= modulus; 11 | } 12 | } 13 | a = (a * BigInt(2)) % modulus; 14 | b >>= BigInt(1); 15 | } 16 | 17 | return accumulator; 18 | } 19 | -------------------------------------------------------------------------------- /src/algorithms/ShanksTonelli.test.ts: -------------------------------------------------------------------------------- 1 | import { CurveType, getModulus } from '../gpu/curveSpecific'; 2 | import { tonelli_shanks } from './ShanksTonelli'; 3 | 4 | const testData = [ 5 | { curve: CurveType.BLS12_377, input: BigInt(4), expected: BigInt(2) }, 6 | { curve: CurveType.BLS12_377, input: BigInt(9), expected: BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239038')}, 7 | { curve: CurveType.BLS12_377, input: BigInt(25), expected: BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239036')}, 8 | { curve: CurveType.BLS12_377, input: BigInt('9657672915538583998542678820329009'), expected: BigInt('8444461749428370424248824938781546531375899335154063827935135182457535585544')}, 9 | { curve: CurveType.BN254, input: BigInt("798273450982734509873540987349587349587"), expected: BigInt("9453787908876337015538631367818595462517703355588148573050277699114259557355") }, 10 | ]; 11 | 12 | describe('ShanksTonelli', () => { 13 | it.each(testData)('returns square root', (args) => { 14 | const fieldModulus = getModulus(args.curve); 15 | const result = tonelli_shanks(args.input, fieldModulus); 16 | expect(result).toBe(args.expected); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/algorithms/ShanksTonelli.ts: -------------------------------------------------------------------------------- 1 | import { CurveType, getModulus } from "../gpu/curveSpecific"; 2 | 3 | const pow_mod = (x: bigint, n: bigint, p: bigint): bigint => { 4 | if (n === BigInt(0)) return BigInt(1); 5 | if (n & BigInt(1)) 6 | return (pow_mod(x, n-BigInt(1), p) * x) % p; 7 | x = pow_mod(x, n/BigInt(2), p); 8 | return (x * x) % p; 9 | } 10 | 11 | /* Takes as input an odd prime p and n < p and returns r 12 | * such that r * r = n [mod p]. */ 13 | export const tonelli_shanks = (n: bigint, p: bigint): bigint => { 14 | let s = BigInt(0); 15 | let q = p - BigInt(1); 16 | while ((q & BigInt(1)) === BigInt(0)) { 17 | q /= BigInt(2); 18 | s++; 19 | } 20 | if (s === BigInt(1)) { 21 | const exp_to_calc = (p+BigInt(1))/BigInt(4); 22 | const r = pow_mod(n, exp_to_calc, p); 23 | if ((r * r) % p === n) 24 | return r; 25 | return BigInt(0); 26 | } 27 | // Find the first quadratic non-residue z by brute-force search 28 | let z = BigInt(1); 29 | while (pow_mod(z, (p-BigInt(1))/BigInt(2), p) !== p - BigInt(1)) { 30 | z++; 31 | } 32 | let c = pow_mod(z, q, p); 33 | const exp_next_calc = (q+BigInt(1))/BigInt(2); 34 | let r = pow_mod(n, exp_next_calc, p); 35 | let t = pow_mod(n, q, p); 36 | console.log('q: ', q); 37 | console.log('s: ', s); 38 | console.log('p: ', p); 39 | console.log('c: ', c); 40 | 41 | let m = s; 42 | while (t !== BigInt(1)) { 43 | let tt = t; 44 | let i = BigInt(0); 45 | while (tt !== BigInt(1)) { 46 | tt = (tt * tt) % p; 47 | i++; 48 | if (i === m) 49 | return BigInt(0); 50 | } 51 | const b = pow_mod(c, pow_mod(BigInt(2), m-i-BigInt(1), p-BigInt(1)), p); 52 | const b2 = (b * b) % p; 53 | r = (r * b) % p; 54 | t = (t * b2) % p; 55 | c = b2; 56 | m = i; 57 | } 58 | if ((r * r) % p === n) return r; 59 | return BigInt(0); 60 | } 61 | 62 | export const preComputedTShanks = (n: bigint, curve: CurveType): bigint => { 63 | let q = BigInt(0); 64 | let s = BigInt(0); 65 | let p = BigInt(1); 66 | let c = BigInt(0); 67 | let exp_next_calc = BigInt(0); 68 | 69 | switch (curve) { 70 | case CurveType.BLS12_377: 71 | q = BigInt('60001509534603559531609739528203892656505753216962260608619555'); 72 | s = BigInt(47); 73 | p = getModulus(curve); 74 | c = BigInt('6924886788847882060123066508223519077232160750698452411071850219367055984476'); 75 | exp_next_calc = BigInt('30000754767301779765804869764101946328252876608481130304309778'); 76 | break; 77 | 78 | default: 79 | throw new Error('Unsupported curve type for Shanks Tonelli'); 80 | } 81 | 82 | let r = pow_mod(n, exp_next_calc, p); 83 | let t = pow_mod(n, q, p); 84 | let m = s; 85 | while (t !== BigInt(1)) { 86 | let tt = t; 87 | let i = BigInt(0); 88 | while (tt !== BigInt(1)) { 89 | tt = (tt * tt) % p; 90 | i++; 91 | if (i === m) 92 | return BigInt(0); 93 | } 94 | const b = pow_mod(c, pow_mod(BigInt(2), m-i-BigInt(1), p-BigInt(1)), p); 95 | const b2 = (b * b) % p; 96 | r = (r * b) % p; 97 | t = (t * b2) % p; 98 | c = b2; 99 | m = i; 100 | } 101 | if ((r * r) % p === n) return r; 102 | return BigInt(0); 103 | } -------------------------------------------------------------------------------- /src/gpu/U32Sizes.ts: -------------------------------------------------------------------------------- 1 | export const U256_SIZE = 8; // 8 u32s to make 256 bits 2 | export const FIELD_SIZE = 8; // 8 u32s to make 256 bits 3 | export const AFFINE_POINT_SIZE = 16; // 2 Fields to make one affine point 4 | export const EXT_POINT_SIZE = 32; // 4 Fields to make one extended point 5 | -------------------------------------------------------------------------------- /src/gpu/curveSpecific.ts: -------------------------------------------------------------------------------- 1 | import { ExtPointType } from "@noble/curves/abstract/edwards"; 2 | import { ProjPointType } from "@noble/curves/abstract/weierstrass"; 3 | import { bn254 } from '@noble/curves/bn254'; 4 | import { bbPoint } from "../barretenberg-wasm-loader/wasm-functions"; 5 | import { FIELD_MODULUS as BLS12_377_FIELD_MODULUS } from "../params/BLS12_377Constants"; 6 | import { FQ_FIELD_MODULUS as BN254_FIELD_MODULUS } from "../params/BN254Constants"; 7 | import { FieldMath } from "../utils/BLS12_377FieldMath"; 8 | import { bigIntToU32Array } from "./utils"; 9 | import { BLS12_377CurveBaseWGSL } from "./wgsl/BLS12-377CurveBaseWGSL"; 10 | import { BLS12_377ParamsWGSL } from "./wgsl/BLS12-377Params"; 11 | import { BN254CurveBaseWGSL } from "./wgsl/BN254CurveBaseWGSL"; 12 | import { BN254ParamsWGSL } from "./wgsl/BN254Params"; 13 | 14 | export const workgroupSize = 64; 15 | 16 | export enum CurveType { 17 | BLS12_377 = 'BLS12_377', 18 | BN254 = 'BN254', 19 | } 20 | 21 | export const getModulus = (curve: CurveType) => { 22 | switch (curve) { 23 | case CurveType.BLS12_377: 24 | return BLS12_377_FIELD_MODULUS; 25 | case CurveType.BN254: 26 | return BN254_FIELD_MODULUS; 27 | default: 28 | throw new Error('Invalid curve type'); 29 | } 30 | }; 31 | 32 | export const getCurveParamsWGSL = (curve: CurveType) => { 33 | switch (curve) { 34 | case CurveType.BLS12_377: 35 | return BLS12_377ParamsWGSL; 36 | case CurveType.BN254: 37 | return BN254ParamsWGSL; 38 | default: 39 | throw new Error('Invalid curve type'); 40 | } 41 | }; 42 | 43 | export const getCurveBaseFunctionsWGSL = (curve: CurveType) => { 44 | switch (curve) { 45 | case CurveType.BLS12_377: 46 | return BLS12_377CurveBaseWGSL 47 | case CurveType.BN254: 48 | return BN254CurveBaseWGSL; 49 | default: 50 | throw new Error('Invalid curve type'); 51 | } 52 | }; 53 | 54 | export const sumExtPoints = (curve: CurveType, flattenedPoints: bigint[]) => { 55 | switch (curve) { 56 | case CurveType.BLS12_377: 57 | return sumExtPointsBLS12_377(flattenedPoints); 58 | case CurveType.BN254: 59 | return sumExtPointsBN254(flattenedPoints); 60 | default: 61 | throw new Error('Invalid curve type'); 62 | } 63 | }; 64 | 65 | const sumExtPointsBLS12_377 = (flattenedPoints: bigint[]) => { 66 | const fieldMath = new FieldMath(); 67 | const pointArray: ExtPointType[] = []; 68 | 69 | // convert big int result to extended points 70 | for (let i = 0; i < flattenedPoints.length; i += 4) { 71 | const x = flattenedPoints[i]; 72 | const y = flattenedPoints[i + 1]; 73 | const t = flattenedPoints[i + 2]; 74 | const z = flattenedPoints[i + 3]; 75 | const point = fieldMath.createPoint(x, y, t, z); 76 | pointArray.push(point); 77 | } 78 | const affineResult = fieldMath.addPoints(pointArray); 79 | const u32XCoord = bigIntToU32Array(affineResult.x); 80 | return u32XCoord; 81 | }; 82 | 83 | const sumExtPointsBN254 = (flattenedPoints: bigint[]) => { 84 | const pointArray: ProjPointType[] = []; 85 | const curve = bn254; 86 | // convert big int result to extended points 87 | for (let i = 0; i < flattenedPoints.length; i += 4) { 88 | const x = flattenedPoints[i]; 89 | const y = flattenedPoints[i + 1]; 90 | const z = flattenedPoints[i + 3]; 91 | const point = new curve.G1.ProjectivePoint(x, y, z); 92 | pointArray.push(point); 93 | } 94 | const extResult = pointArray.reduce((acc, point) => { 95 | return bn254AddPoints(acc, point)}, curve.G1.ProjectivePoint.ZERO); 96 | const z = extResult.pz; 97 | const iz = curve.fields.Fp.inv(z); 98 | const iz2 = curve.fields.Fp.sqr(iz); 99 | const actualResult = curve.fields.Fp.mul(extResult.px, iz2); 100 | // const affineResult = extResult.toAffine(); 101 | const u32XCoord = bigIntToU32Array(actualResult); 102 | return u32XCoord; 103 | }; 104 | 105 | export const bn254BulkTSMulPoints = (points1: bbPoint[], scalars: string[]) => { 106 | const results: string[] = []; 107 | for (let i = 0; i < points1.length; i++) { 108 | const point = new bn254.G1.ProjectivePoint(BigInt(points1[i].x), BigInt(points1[i].y), BigInt(1)); 109 | const scalar = BigInt(scalars[i]); 110 | const result = bn254PointScalar(point, scalar); 111 | const affineX = bn254NormalizePointX(result); 112 | results.push(affineX.toString()); 113 | } 114 | return results; 115 | }; 116 | 117 | export const bn254AddPoints = (p1: ProjPointType, p2: ProjPointType) => { 118 | const fp = bn254.fields.Fp; 119 | if (p1.px === BigInt(0) && p1.py === BigInt(1) && p1.pz === BigInt(0)) { 120 | return p2; 121 | } 122 | if (p2.px === BigInt(0) && p2.py === BigInt(1) && p2.pz === BigInt(0)) { 123 | return p1; 124 | } 125 | let z1z1 = fp.mul(p1.pz, p1.pz); 126 | const z2z2 = fp.mul(p2.pz, p2.pz); 127 | let s2 = fp.mul(z1z1, p1.pz); 128 | let u2 = fp.mul(z1z1, p2.px); 129 | s2 = fp.mul(s2, p2.py); 130 | let u1 = fp.mul(z2z2, p1.px); 131 | let s1 = fp.mul(z2z2, p2.pz); 132 | s1 = fp.mul(s1, p1.py); 133 | const s_sub = fp.sub(s2, s1); 134 | const f = fp.add(s_sub, s_sub); 135 | if (f === BigInt(0)) { 136 | return bn254DoublePoint(p1); 137 | } 138 | const h = fp.sub(u2, u1); 139 | let i = fp.add(h, h); 140 | i = fp.mul(i, i); 141 | let j = fp.mul(h, i); 142 | u1 = fp.mul(u1, i); 143 | u2 = fp.add(u1, u1); 144 | u2 = fp.add(u2, j); 145 | let x_result = fp.mul(f, f); 146 | x_result = fp.sub(x_result, u2); 147 | j = fp.mul(j, s1); 148 | j = fp.add(j, j); 149 | let y_result = fp.sub(u1, x_result); 150 | y_result = fp.mul(f, y_result); 151 | y_result = fp.sub(y_result, j); 152 | let z_result = fp.add(p1.pz, p2.pz); 153 | z1z1 = fp.add(z1z1, z2z2); 154 | z_result = fp.mul(z_result, z_result); 155 | z_result = fp.sub(z_result, z1z1); 156 | z_result = fp.mul(z_result, h); 157 | return new bn254.G1.ProjectivePoint(x_result, y_result, z_result); 158 | }; 159 | 160 | export const bn254PointScalar = (p: ProjPointType, scalar: bigint) => { 161 | let result = bn254.G1.ProjectivePoint.ZERO; 162 | let temp = p; 163 | let scalar_iter = scalar; 164 | while (scalar_iter !== 0n) { 165 | if ((scalar_iter & 1n) === 1n) { 166 | result = bn254AddPoints(result, temp); 167 | } 168 | temp = bn254DoublePoint(temp); 169 | scalar_iter = scalar_iter >> 1n; 170 | } 171 | 172 | return result; 173 | }; 174 | 175 | export const bn254NormalizePointX = (p: ProjPointType) => { 176 | const z = p.pz; 177 | const iz = bn254.fields.Fp.inv(z); 178 | const iz2 = bn254.fields.Fp.sqr(iz); 179 | return bn254.fields.Fp.mul(p.px, iz2); 180 | }; 181 | 182 | const bn254DoublePoint = (p: ProjPointType) => { 183 | const fp = bn254.fields.Fp; 184 | let T0 = fp.mul(p.px, p.px); 185 | let T1 = fp.mul(p.py, p.py); 186 | let T2 = fp.mul(T1, T1); 187 | T1 = fp.add(p.px, T1); 188 | T1 = fp.mul(T1, T1); 189 | let T3 = fp.add(T0, T2); 190 | T1 = fp.sub(T1, T3); 191 | T1 = fp.add(T1, T1); 192 | T3 = fp.add(T0, T0); 193 | T3 = fp.add(T3, T0); 194 | let z_result = fp.add(p.pz, p.pz); 195 | z_result = fp.mul(z_result, p.py); 196 | T0 = fp.add(T1, T1); 197 | let x_result = fp.mul(T3, T3); 198 | x_result = fp.sub(x_result, T0); 199 | T2 = fp.add(T2, T2); 200 | T2 = fp.add(T2, T2); 201 | T2 = fp.add(T2, T2); 202 | let y_result = fp.sub(T1, x_result); 203 | y_result = fp.mul(T3, y_result); 204 | y_result = fp.sub(y_result, T2); 205 | return new bn254.G1.ProjectivePoint(x_result, y_result, z_result); 206 | }; -------------------------------------------------------------------------------- /src/gpu/entries/bls12-377Algorithms/aleoIsOwnerEntry.ts: -------------------------------------------------------------------------------- 1 | import { CurveWGSL } from "../../wgsl/Curve"; 2 | import { FieldModulusWGSL } from "../../wgsl/FieldModulus"; 3 | import { AleoPoseidonWGSL } from "../../wgsl/AleoPoseidon"; 4 | import { AleoPoseidonConstantsWGSL } from "../../wgsl/AleoPoseidonConstants"; 5 | import { U256WGSL } from "../../wgsl/U256"; 6 | import { batchedEntry } from "../entryCreator" 7 | import { FIELD_SIZE } from "../../U32Sizes"; 8 | import { gpuU32Inputs } from "../../utils"; 9 | import { CurveType, getCurveBaseFunctionsWGSL, getCurveParamsWGSL } from "../../curveSpecific"; 10 | import { prune } from "../../prune"; 11 | 12 | export const is_owner = async ( 13 | cipherTextAffineCoords: gpuU32Inputs, 14 | encryptedOwnerXs: gpuU32Inputs, 15 | aleoMds: gpuU32Inputs, 16 | aleoRoundConstants: gpuU32Inputs, 17 | scalar: gpuU32Inputs, 18 | address_x: gpuU32Inputs, 19 | batchSize?: number 20 | ) => { 21 | const shaderEntry = ` 22 | const EMBEDDED_SCALAR: Field = Field( 23 | array(${scalar.u32Inputs[0]}, ${scalar.u32Inputs[1]}, ${scalar.u32Inputs[2]}, ${scalar.u32Inputs[3]}, ${scalar.u32Inputs[4]}, ${scalar.u32Inputs[5]}, ${scalar.u32Inputs[6]}, ${scalar.u32Inputs[7]}) 24 | ); 25 | 26 | const EMBEDDED_ADDRESS_X: Field = Field( 27 | array(${address_x.u32Inputs[0]}, ${address_x.u32Inputs[1]}, ${address_x.u32Inputs[2]}, ${address_x.u32Inputs[3]}, ${address_x.u32Inputs[4]}, ${address_x.u32Inputs[5]}, ${address_x.u32Inputs[6]}, ${address_x.u32Inputs[7]}) 28 | ); 29 | 30 | @group(0) @binding(0) 31 | var input1: array; 32 | @group(0) @binding(1) 33 | var owner_field_x: array; 34 | @group(0) @binding(2) 35 | var aleoMds: array, 9>; 36 | @group(0) @binding(3) 37 | var aleoRoundConstants: array, 39>; 38 | @group(0) @binding(4) 39 | var output: array; 40 | 41 | @compute @workgroup_size(64) 42 | fn main( 43 | @builtin(global_invocation_id) 44 | global_id : vec3 45 | ) { 46 | var p1 = input1[global_id.x]; 47 | var p1_t = field_multiply(p1.x, p1.y); 48 | var z = U256_ONE; 49 | var ext_p1 = Point(p1.x, p1.y, p1_t, z); 50 | 51 | var multiplied = mul_point_windowed(ext_p1, EMBEDDED_SCALAR); 52 | var z_inverse = field_inverse(multiplied.z); 53 | var result = field_multiply(multiplied.x, z_inverse); 54 | 55 | var hash = aleo_poseidon(result); 56 | 57 | var owner_to_compare = field_sub(owner_field_x[global_id.x], hash); 58 | 59 | output[global_id.x] = field_sub(owner_to_compare, EMBEDDED_ADDRESS_X); 60 | } 61 | `; 62 | 63 | const curve = CurveType.BLS12_377; 64 | 65 | const shaderModules = [ 66 | U256WGSL, 67 | AleoPoseidonConstantsWGSL, 68 | getCurveParamsWGSL(curve), 69 | FieldModulusWGSL, 70 | AleoPoseidonWGSL, 71 | getCurveBaseFunctionsWGSL(curve), 72 | CurveWGSL 73 | ]; 74 | const shaderCode = prune( 75 | shaderModules.join(''), 76 | ['field_multiply', 'mul_point_windowed', 'field_inverse', 'aleo_poseidon', 'field_sub'] 77 | ) + shaderEntry; 78 | 79 | return await batchedEntry([cipherTextAffineCoords, encryptedOwnerXs, aleoMds, aleoRoundConstants], shaderCode, FIELD_SIZE, batchSize, [0, 1]); 80 | } 81 | 82 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 83 | (window as any).is_owner = is_owner; -------------------------------------------------------------------------------- /src/gpu/entries/bls12-377Algorithms/aleoPoseidonEntry.ts: -------------------------------------------------------------------------------- 1 | import { BLS12_377ParamsWGSL } from "../../wgsl/BLS12-377Params"; 2 | import { FieldModulusWGSL } from "../../wgsl/FieldModulus"; 3 | import { AleoPoseidonWGSL } from "../../wgsl/AleoPoseidon"; 4 | import { AleoPoseidonConstantsWGSL } from "../../wgsl/AleoPoseidonConstants"; 5 | import { U256WGSL } from "../../wgsl/U256"; 6 | import { batchedEntry } from "../entryCreator"; 7 | import { bigIntsToU32Array, gpuU32Inputs } from "../../utils"; 8 | import { FIELD_SIZE } from "../../U32Sizes"; 9 | import { prune } from "../../prune"; 10 | import { aleoMdStrings, aleoRoundConstantStrings } from "../../../params/AleoPoseidonParams"; 11 | 12 | export const aleo_poseidon = async ( 13 | fields: gpuU32Inputs, 14 | batchSize?: number 15 | ) => { 16 | const aleoMds = { 17 | u32Inputs: bigIntsToU32Array(aleoMdStrings.map((arr) => arr.map((str) => BigInt(str))).flat()), 18 | individualInputSize: FIELD_SIZE 19 | } 20 | const roundConstants = { 21 | u32Inputs: bigIntsToU32Array(aleoRoundConstantStrings.map((arr) => arr.map((str) => BigInt(str))).flat()), 22 | individualInputSize: FIELD_SIZE 23 | } 24 | const shaderEntry = ` 25 | @group(0) @binding(0) 26 | var input1: array; 27 | @group(0) @binding(1) 28 | var aleoMds: array, 9>; 29 | @group(0) @binding(2) 30 | var aleoRoundConstants: array,39>; 31 | @group(0) @binding(3) 32 | var output: array; 33 | 34 | @compute @workgroup_size(64) 35 | fn main( 36 | @builtin(global_invocation_id) global_id : vec3 37 | ) { 38 | var result = aleo_poseidon(input1[global_id.x]); 39 | output[global_id.x] = result; 40 | } 41 | `; 42 | 43 | const shaderModules = [AleoPoseidonConstantsWGSL, U256WGSL, BLS12_377ParamsWGSL, FieldModulusWGSL, AleoPoseidonWGSL]; 44 | const shaderCode = prune( 45 | shaderModules.join('\n'), 46 | ['aleo_poseidon'] 47 | ) + shaderEntry; 48 | 49 | return await batchedEntry([fields, aleoMds, roundConstants], shaderCode, FIELD_SIZE, batchSize, [0]); 50 | }; 51 | 52 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 53 | (window as any).aleo_poseidon = aleo_poseidon; -------------------------------------------------------------------------------- /src/gpu/entries/curve/curveAddPoints.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntsToU32Array, gpuU32Inputs, gpuU32PuppeteerString, u32ArrayToBigInts } from '../../utils'; 4 | import { AFFINE_POINT_SIZE } from '../../U32Sizes'; 5 | 6 | describe('curveAddPoints', () => { 7 | let browser: Browser; 8 | beforeAll(async () => { 9 | browser = await puppeteer.launch({ 10 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 11 | devtools: true, 12 | headless: false, 13 | args: ['#enable-webgpu-developer-features'] 14 | }); 15 | 16 | const page = (await browser.pages())[0]; 17 | await page.goto("http://localhost:4000"); 18 | }); 19 | 20 | afterAll(async () => { 21 | await browser.close(); 22 | }); 23 | 24 | it.each([ 25 | [ 26 | BigInt('2267804453849548326441105932178046088516965666196959520730613219383769450836'), 27 | BigInt('1072951797970064815719882445234806898669878320855410398424678322112654070151'), 28 | BigInt('4407911307578806921901458939347649080208231626630832716981525978619048166152'), 29 | BigInt('6565636154300619788727298316911861941870758292624133564236686502629302140344'), 30 | BigInt('5786258225753402907650271726047597104252057332301870339194408122376703625189'), 31 | // BigInt('3590369170636338728341911025963343651055585602080772103462129809241739037174') 32 | ], 33 | [ 34 | BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246'), 35 | BigInt('8134280397689638111748378379571739274369602049665521098046934931245960532166'), 36 | BigInt(0), 37 | BigInt(1), 38 | BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246'), 39 | // BigInt('8134280397689638111748378379571739274369602049665521098046934931245960532166'), 40 | ], 41 | [ 42 | BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246'), 43 | BigInt('8134280397689638111748378379571739274369602049665521098046934931245960532166'), 44 | BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246'), 45 | BigInt('8134280397689638111748378379571739274369602049665521098046934931245960532166'), 46 | BigInt('7304662912603109101654342147238231070235863099037011884568440290807776100174') 47 | ] 48 | ])('should add affine points together', async ( 49 | p1x: bigint, 50 | p1y: bigint, 51 | p2x: bigint, 52 | p2y: bigint, 53 | resultx: bigint, 54 | ) => { 55 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntsToU32Array([p1x, p1y]), individualInputSize: AFFINE_POINT_SIZE }; 56 | const u32Input2: gpuU32Inputs = { u32Inputs: bigIntsToU32Array([p2x, p2y]), individualInputSize: AFFINE_POINT_SIZE }; 57 | const result = await ((await browser.pages())[0]).evaluate(`(point_add)(${gpuU32PuppeteerString(u32Input1)}, ${gpuU32PuppeteerString(u32Input2)})`); 58 | const arr = Object.values(result as object); 59 | const uint32ArrayResult = new Uint32Array(arr); 60 | const bigIntsResult = u32ArrayToBigInts(uint32ArrayResult); 61 | 62 | expect(bigIntsResult[0].toString()).toEqual(resultx.toString()); 63 | }); 64 | }); -------------------------------------------------------------------------------- /src/gpu/entries/curve/curveAddPointsEntry.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_SIZE } from "../../U32Sizes"; 2 | import { CurveType, getCurveBaseFunctionsWGSL, getCurveParamsWGSL } from "../../curveSpecific"; 3 | import { prune } from "../../prune"; 4 | import { gpuU32Inputs } from "../../utils"; 5 | import { CurveWGSL } from "../../wgsl/Curve"; 6 | import { FieldModulusWGSL } from "../../wgsl/FieldModulus"; 7 | import { U256WGSL } from "../../wgsl/U256"; 8 | import { batchedEntry } from "../entryCreator" 9 | 10 | export const point_add = async ( 11 | curve: CurveType, 12 | points_a: gpuU32Inputs, 13 | points_b: gpuU32Inputs, 14 | batchSize?: number 15 | ) => { 16 | const shaderEntry = ` 17 | @group(0) @binding(0) 18 | var input1: array; 19 | @group(0) @binding(1) 20 | var input2: array; 21 | @group(0) @binding(2) 22 | var output: array; 23 | 24 | @compute @workgroup_size(64) 25 | fn main( 26 | @builtin(global_invocation_id) 27 | global_id : vec3 28 | ) { 29 | var p1 = input1[global_id.x]; 30 | var p1_t = field_multiply(p1.x, p1.y); 31 | var p2 = input2[global_id.x]; 32 | var p2_t = field_multiply(p2.x, p2.y); 33 | var z = U256_ONE; 34 | var ext_p1 = Point(p1.x, p1.y, p1_t, z); 35 | var ext_p2 = Point(p2.x, p2.y, p2_t, z); 36 | 37 | var added = add_points(ext_p1, ext_p2); 38 | var x_normalized = normalize_x(added.x, added.z); 39 | 40 | output[global_id.x] = x_normalized; 41 | } 42 | `; 43 | 44 | const curveParams = getCurveParamsWGSL(curve); 45 | const curveBaseFunctions = getCurveBaseFunctionsWGSL(curve); 46 | 47 | const shaderModules = [U256WGSL, curveParams, FieldModulusWGSL, curveBaseFunctions, CurveWGSL]; 48 | const shaderCode = prune( 49 | shaderModules.join('\n'), 50 | ['field_multiply', 'add_points', 'normalize_x'] 51 | ) + shaderEntry; 52 | 53 | return await batchedEntry([points_a, points_b], shaderCode, FIELD_SIZE, batchSize); 54 | } 55 | 56 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 57 | (window as any).point_add = point_add; -------------------------------------------------------------------------------- /src/gpu/entries/curve/curveDoublePoint.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntsToU32Array, gpuU32Inputs, gpuU32PuppeteerString, u32ArrayToBigInts } from '../../utils'; 4 | import { AFFINE_POINT_SIZE } from '../../U32Sizes'; 5 | 6 | describe('curveDoublePoints', () => { 7 | let browser: Browser; 8 | beforeAll(async () => { 9 | browser = await puppeteer.launch({ 10 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 11 | devtools: true, 12 | headless: false, 13 | args: ['#enable-webgpu-developer-features'] 14 | }); 15 | 16 | const page = (await browser.pages())[0]; 17 | await page.goto("http://localhost:4000"); 18 | }); 19 | 20 | afterAll(async () => { 21 | await browser.close(); 22 | }); 23 | 24 | it.each([ 25 | [ 26 | BigInt('7567318425042049695485063481352884626263173541493743764753928133860027560480'), 27 | BigInt('6153410899968666564625001831730219854362172909505947924193488412955254022111'), 28 | BigInt('5042473777803417606579440401406822102329732371743950988738806767808616709467'), 29 | ], 30 | [ 31 | BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246'), 32 | BigInt('8134280397689638111748378379571739274369602049665521098046934931245960532166'), 33 | BigInt('7304662912603109101654342147238231070235863099037011884568440290807776100174') 34 | ] 35 | ])('should double affine points', async ( 36 | p1x: bigint, 37 | p1y: bigint, 38 | resultx: bigint, 39 | ) => { 40 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntsToU32Array([p1x, p1y]), individualInputSize: AFFINE_POINT_SIZE }; 41 | const result = await ((await browser.pages())[0]).evaluate(`(point_double)(${gpuU32PuppeteerString(u32Input1)})`); 42 | const arr = Object.values(result as object); 43 | const uint32ArrayResult = new Uint32Array(arr); 44 | const bigIntsResult = u32ArrayToBigInts(uint32ArrayResult); 45 | 46 | expect(bigIntsResult[0].toString()).toEqual(resultx.toString()); 47 | }); 48 | }); -------------------------------------------------------------------------------- /src/gpu/entries/curve/curveDoublePointEntry.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_SIZE } from "../../U32Sizes"; 2 | import { CurveType, getCurveBaseFunctionsWGSL, getCurveParamsWGSL } from "../../curveSpecific"; 3 | import { prune } from "../../prune"; 4 | import { gpuU32Inputs } from "../../utils"; 5 | import { CurveWGSL } from "../../wgsl/Curve"; 6 | import { FieldModulusWGSL } from "../../wgsl/FieldModulus"; 7 | import { U256WGSL } from "../../wgsl/U256"; 8 | import { batchedEntry } from "../entryCreator" 9 | 10 | export const point_double = async ( 11 | curve: CurveType, 12 | points: gpuU32Inputs, 13 | batchSize?: number 14 | ) => { 15 | const shaderEntry = ` 16 | @group(0) @binding(0) 17 | var input1: array; 18 | @group(0) @binding(1) 19 | var output: array; 20 | 21 | @compute @workgroup_size(64) 22 | fn main( 23 | @builtin(global_invocation_id) 24 | global_id : vec3 25 | ) { 26 | var p1 = input1[global_id.x]; 27 | var p1_t = field_multiply(p1.x, p1.y); 28 | var z = U256_ONE; 29 | var ext_p1 = Point(p1.x, p1.y, p1_t, z); 30 | 31 | var doubled = double_point(ext_p1); 32 | var x_normalized = normalize_x(doubled.x, doubled.z); 33 | 34 | output[global_id.x] = x_normalized; 35 | } 36 | `; 37 | 38 | const shaderModules = [ 39 | U256WGSL, 40 | getCurveParamsWGSL(curve), 41 | FieldModulusWGSL, 42 | getCurveBaseFunctionsWGSL(curve), 43 | CurveWGSL 44 | ]; 45 | const shaderCode = prune( 46 | shaderModules.join('\n'), 47 | ['field_multiply', 'double_point', 'normalize_x'] 48 | ) + shaderEntry; 49 | 50 | return await batchedEntry([points], shaderCode, FIELD_SIZE, batchSize); 51 | } 52 | 53 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 54 | (window as any).point_double = point_double; -------------------------------------------------------------------------------- /src/gpu/entries/curve/curveMulPoint.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntToU32Array, bigIntsToU32Array, gpuU32Inputs, gpuU32PuppeteerString, u32ArrayToBigInts } from '../../utils'; 4 | import { AFFINE_POINT_SIZE, FIELD_SIZE } from '../../U32Sizes'; 5 | 6 | describe('curveMulPoint', () => { 7 | let browser: Browser; 8 | beforeAll(async () => { 9 | browser = await puppeteer.launch({ 10 | // Might need to be configurable at some point. 11 | // '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome' 12 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 13 | devtools: true, 14 | headless: false, 15 | args: ['#enable-webgpu-developer-features'] 16 | }); 17 | 18 | const page = (await browser.pages())[0]; 19 | await page.goto("http://localhost:4000"); 20 | }); 21 | 22 | afterAll(async () => { 23 | await browser.close(); 24 | }); 25 | 26 | it.each([ 27 | [ 28 | BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246'), 29 | BigInt('8134280397689638111748378379571739274369602049665521098046934931245960532166'), 30 | BigInt('1753533570350686550323082834194063544688355123444645930667634514069517491627'), 31 | BigInt('5324992470787461040823919570440348586607207885188029730405305593254964962313') 32 | ], 33 | [ 34 | BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246'), 35 | BigInt('8134280397689638111748378379571739274369602049665521098046934931245960532166'), 36 | BigInt('1'), 37 | BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246') 38 | ], 39 | [ 40 | BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246'), 41 | BigInt('8134280397689638111748378379571739274369602049665521098046934931245960532166'), 42 | BigInt('2'), 43 | BigInt('7304662912603109101654342147238231070235863099037011884568440290807776100174') 44 | ] 45 | ])('should multiply affine point by scalar', async ( 46 | p1x: bigint, 47 | p1y: bigint, 48 | scalar: bigint, 49 | resultx: bigint, 50 | ) => { 51 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntsToU32Array([p1x, p1y]), individualInputSize: AFFINE_POINT_SIZE }; 52 | const u32Input2: gpuU32Inputs = { u32Inputs: bigIntToU32Array(scalar), individualInputSize: FIELD_SIZE }; 53 | const result = await ((await browser.pages())[0]).evaluate(`(point_mul)(${gpuU32PuppeteerString(u32Input1)}, ${gpuU32PuppeteerString(u32Input2)})`); 54 | const arr = Object.values(result as object); 55 | const uint32ArrayResult = new Uint32Array(arr); 56 | const bigIntsResult = u32ArrayToBigInts(uint32ArrayResult); 57 | 58 | expect(bigIntsResult[0].toString()).toEqual(resultx.toString()); 59 | }); 60 | }); -------------------------------------------------------------------------------- /src/gpu/entries/curve/curveMulPointEntry.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_SIZE } from "../../U32Sizes"; 2 | import { CurveType, getCurveBaseFunctionsWGSL, getCurveParamsWGSL } from "../../curveSpecific"; 3 | import { gpuU32Inputs, u32ArrayToBigInts } from "../../utils"; 4 | import { CurveWGSL } from "../../wgsl/Curve"; 5 | import { FieldModulusWGSL } from "../../wgsl/FieldModulus"; 6 | import { U256WGSL } from "../../wgsl/U256"; 7 | import { batchedEntry } from "../entryCreator" 8 | import { prune } from "../../prune"; 9 | 10 | export const point_mul = async ( 11 | curve: CurveType, 12 | points: gpuU32Inputs, 13 | scalars: gpuU32Inputs, 14 | batchSize?: number 15 | ) => { 16 | const shaderEntry = ` 17 | @group(0) @binding(0) 18 | var input1: array; 19 | @group(0) @binding(1) 20 | var input2: array; 21 | @group(0) @binding(2) 22 | var output: array; 23 | 24 | @compute @workgroup_size(64) 25 | fn main( 26 | @builtin(global_invocation_id) 27 | global_id : vec3 28 | ) { 29 | var p1 = input1[global_id.x]; 30 | var p1_t = field_multiply(p1.x, p1.y); 31 | var z = U256_ONE; 32 | var ext_p1 = Point(p1.x, p1.y, p1_t, z); 33 | 34 | var scalar = input2[global_id.x]; 35 | 36 | var multiplied = mul_point(ext_p1, scalar); 37 | var x_normalized = normalize_x(multiplied.x, multiplied.z); 38 | 39 | output[global_id.x] = x_normalized; 40 | } 41 | `; 42 | 43 | const shaderModules = [ 44 | U256WGSL, 45 | getCurveParamsWGSL(curve), 46 | FieldModulusWGSL, 47 | getCurveBaseFunctionsWGSL(curve), 48 | CurveWGSL 49 | ]; 50 | 51 | const shaderCode = prune( 52 | shaderModules.join(''), 53 | ['field_multiply', 'mul_point', 'field_inverse', 'normalize_x'] 54 | ) + shaderEntry; 55 | 56 | return await batchedEntry([points, scalars], shaderCode, FIELD_SIZE, batchSize); 57 | } 58 | 59 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 60 | (window as any).point_mul = point_mul; -------------------------------------------------------------------------------- /src/gpu/entries/curve/curveMulPointWindowed.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntToU32Array, bigIntsToU32Array, gpuU32Inputs, gpuU32PuppeteerString, u32ArrayToBigInts } from '../../utils'; 4 | import { AFFINE_POINT_SIZE, FIELD_SIZE } from '../../U32Sizes'; 5 | 6 | describe('curveMulPointWindowed', () => { 7 | let browser: Browser; 8 | beforeAll(async () => { 9 | browser = await puppeteer.launch({ 10 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 11 | devtools: true, 12 | headless: false, 13 | args: ['#enable-webgpu-developer-features'] 14 | }); 15 | 16 | const page = (await browser.pages())[0]; 17 | await page.goto("http://localhost:4000"); 18 | }); 19 | 20 | afterAll(async () => { 21 | await browser.close(); 22 | }); 23 | 24 | it.each([ 25 | [ 26 | BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246'), 27 | BigInt('8134280397689638111748378379571739274369602049665521098046934931245960532166'), 28 | BigInt('1753533570350686550323082834194063544688355123444645930667634514069517491627'), 29 | BigInt('5324992470787461040823919570440348586607207885188029730405305593254964962313') 30 | ], 31 | [ 32 | BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246'), 33 | BigInt('8134280397689638111748378379571739274369602049665521098046934931245960532166'), 34 | BigInt('1'), 35 | BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246') 36 | ], 37 | [ 38 | BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246'), 39 | BigInt('8134280397689638111748378379571739274369602049665521098046934931245960532166'), 40 | BigInt('2'), 41 | BigInt('7304662912603109101654342147238231070235863099037011884568440290807776100174') 42 | ] 43 | ])('should multiply affine point by scalar', async ( 44 | p1x: bigint, 45 | p1y: bigint, 46 | scalar: bigint, 47 | resultx: bigint, 48 | ) => { 49 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntsToU32Array([p1x, p1y]), individualInputSize: AFFINE_POINT_SIZE }; 50 | const u32Input2: gpuU32Inputs = { u32Inputs: bigIntToU32Array(scalar), individualInputSize: FIELD_SIZE }; 51 | const result = await ((await browser.pages())[0]).evaluate(`(point_mul_windowed)(${gpuU32PuppeteerString(u32Input1)}, ${gpuU32PuppeteerString(u32Input2)})`); 52 | const arr = Object.values(result as object); 53 | const uint32ArrayResult = new Uint32Array(arr); 54 | const bigIntsResult = u32ArrayToBigInts(uint32ArrayResult); 55 | 56 | expect(bigIntsResult[0].toString()).toEqual(resultx.toString()); 57 | }); 58 | }); -------------------------------------------------------------------------------- /src/gpu/entries/curve/curveMulPointWindowedEntry.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_SIZE } from "../../U32Sizes"; 2 | import { CurveType, getCurveBaseFunctionsWGSL, getCurveParamsWGSL } from "../../curveSpecific"; 3 | import { gpuU32Inputs } from "../../utils"; 4 | import { CurveWGSL } from "../../wgsl/Curve"; 5 | import { FieldModulusWGSL } from "../../wgsl/FieldModulus"; 6 | import { U256WGSL } from "../../wgsl/U256"; 7 | import { batchedEntry } from "../entryCreator" 8 | import { prune } from "../../prune"; 9 | 10 | export const point_mul_windowed = async ( 11 | curve: CurveType, 12 | input1: gpuU32Inputs, 13 | input2: gpuU32Inputs, 14 | batchSize?: number 15 | ) => { 16 | const shaderEntry = ` 17 | @group(0) @binding(0) 18 | var input1: array; 19 | @group(0) @binding(1) 20 | var input2: array; 21 | @group(0) @binding(2) 22 | var output: array; 23 | 24 | @compute @workgroup_size(64) 25 | fn main( 26 | @builtin(global_invocation_id) 27 | global_id : vec3 28 | ) { 29 | var p1 = input1[global_id.x]; 30 | var p1_t = field_multiply(p1.x, p1.y); 31 | var z = U256_ONE; 32 | var ext_p1 = Point(p1.x, p1.y, p1_t, z); 33 | 34 | var scalar = input2[global_id.x]; 35 | 36 | var multiplied = mul_point_windowed(ext_p1, scalar); 37 | var x_normalized = normalize_x(multiplied.x, multiplied.z); 38 | 39 | output[global_id.x] = x_normalized; 40 | } 41 | `; 42 | 43 | const shaderModules = [ 44 | U256WGSL, 45 | getCurveParamsWGSL(curve), 46 | FieldModulusWGSL, 47 | getCurveBaseFunctionsWGSL(curve), 48 | CurveWGSL 49 | ]; 50 | 51 | const shaderCode = prune( 52 | shaderModules.join(''), 53 | ['field_multiply', 'mul_point_windowed', 'field_inverse', 'normalize_x'] 54 | ) + shaderEntry; 55 | 56 | return await batchedEntry([input1, input2], shaderCode, FIELD_SIZE, batchSize); 57 | } 58 | 59 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 60 | (window as any).point_mul_windowed = point_mul_windowed; -------------------------------------------------------------------------------- /src/gpu/entries/entryCreator.ts: -------------------------------------------------------------------------------- 1 | import { chunkArray, chunkGPUInputs, gpuU32Inputs } from "../utils"; 2 | 3 | export interface entryOptions { 4 | u32SizePerOutput: number, 5 | batchSize?: number 6 | } 7 | 8 | export const batchedEntry = async( 9 | inputData: gpuU32Inputs[], 10 | shaderCode: string, 11 | u32SizePerOutput: number, 12 | batchSize?: number, 13 | inputsToBatch?: number[] 14 | ) => { 15 | const u32SizePerFirstInput = inputData[0].individualInputSize; 16 | const totalInputs = inputData[0].u32Inputs.length/ u32SizePerFirstInput; 17 | const totalExpectedOutputs = totalInputs; 18 | batchSize = batchSize ?? totalInputs; 19 | inputsToBatch = inputsToBatch ?? []; // default to batching all inputs 20 | let chunkedInputs = [ inputData ]; 21 | if (batchSize < totalInputs) { 22 | chunkedInputs = chunkGPUInputs(inputData, batchSize, inputsToBatch); 23 | } 24 | const outputResult: Uint32Array = new Uint32Array(totalExpectedOutputs * u32SizePerOutput); 25 | for (let i = 0; i < chunkedInputs.length; i++) { 26 | const batchResult = await entry(chunkedInputs[i], shaderCode, u32SizePerOutput); 27 | outputResult.set(batchResult, i * batchSize * u32SizePerOutput); 28 | } 29 | 30 | return outputResult; 31 | }; 32 | 33 | export const entry = async( 34 | inputData: gpuU32Inputs[], 35 | shaderCode: string, 36 | u32SizePerOutput: number 37 | ) => { 38 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 39 | const device = (await getDevice())!; 40 | const allBuffers: GPUBuffer[] = []; 41 | 42 | const numInputs = inputData[0].u32Inputs.length / inputData[0].individualInputSize; 43 | 44 | const module = device.createShaderModule({ 45 | code: shaderCode 46 | }); 47 | 48 | const gpuBufferInputs = inputData.map((data) => createU32ArrayInputBuffer(device, data.u32Inputs)); 49 | 50 | // Result Matrix 51 | const resultBufferSize = Uint32Array.BYTES_PER_ELEMENT * numInputs * u32SizePerOutput; 52 | const resultBuffer = device.createBuffer({ 53 | size: resultBufferSize, 54 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC 55 | }); 56 | 57 | // Bind group layout and bind group 58 | const bindGroupLayout = createBindGroupLayout(device, gpuBufferInputs); 59 | const bindGroup = createBindGroup(device, bindGroupLayout, gpuBufferInputs, resultBuffer); 60 | 61 | // Pipeline setup 62 | const computePipeline = device.createComputePipeline({ 63 | layout: device.createPipelineLayout({ 64 | bindGroupLayouts: [bindGroupLayout] 65 | }), 66 | compute: { 67 | module: module, 68 | entryPoint: "main" 69 | } 70 | }); 71 | 72 | // Commands submission 73 | const commandEncoder = device.createCommandEncoder(); 74 | 75 | const passEncoder = commandEncoder.beginComputePass(); 76 | passEncoder.setPipeline(computePipeline); 77 | passEncoder.setBindGroup(0, bindGroup); 78 | const workgroupCount = Math.ceil(numInputs / 64); 79 | passEncoder.dispatchWorkgroups(workgroupCount); 80 | passEncoder.end(); 81 | 82 | // Get a GPU buffer for reading in an unmapped state. 83 | const gpuReadBuffer = device.createBuffer({ 84 | size: resultBufferSize, 85 | usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ 86 | }); 87 | 88 | allBuffers.push(...gpuBufferInputs); 89 | allBuffers.push(resultBuffer); 90 | allBuffers.push(gpuReadBuffer); 91 | 92 | // Encode commands for copying buffer to buffer. 93 | commandEncoder.copyBufferToBuffer( 94 | resultBuffer /* source buffer */, 95 | 0 /* source offset */, 96 | gpuReadBuffer /* destination buffer */, 97 | 0 /* destination offset */, 98 | resultBufferSize /* size */ 99 | ); 100 | 101 | // Submit GPU commands. 102 | const gpuCommands = commandEncoder.finish(); 103 | device.queue.submit([gpuCommands]); 104 | 105 | // Read buffer. 106 | await gpuReadBuffer.mapAsync(GPUMapMode.READ); 107 | const arrayBuffer = gpuReadBuffer.getMappedRange(); 108 | const result = new Uint32Array(arrayBuffer.slice(0)); 109 | gpuReadBuffer.unmap(); 110 | 111 | // Destroy all buffers 112 | for (const buffer of allBuffers) { 113 | buffer.destroy(); 114 | } 115 | device.destroy(); 116 | 117 | return result; 118 | } 119 | 120 | const getDevice = async () => { 121 | if (!("gpu" in navigator)) { 122 | console.log( 123 | "WebGPU is not supported. Enable chrome://flags/#enable-unsafe-webgpu flag." 124 | ); 125 | return; 126 | } 127 | 128 | const adapter = await navigator.gpu.requestAdapter(); 129 | if (!adapter) { 130 | console.log("Failed to get GPU adapter."); 131 | return; 132 | } 133 | return await adapter.requestDevice(); 134 | }; 135 | 136 | const createU32ArrayInputBuffer = (device: GPUDevice, uint32s: Uint32Array) => { 137 | const gpuBufferU32Inputs = device.createBuffer({ 138 | mappedAtCreation: true, 139 | size: uint32s.byteLength, 140 | usage: GPUBufferUsage.STORAGE 141 | }); 142 | const arrayBufferInput = gpuBufferU32Inputs.getMappedRange(); 143 | new Uint32Array(arrayBufferInput).set(uint32s); 144 | gpuBufferU32Inputs.unmap(); 145 | return gpuBufferU32Inputs; 146 | }; 147 | 148 | const createBindGroupLayout = (device: GPUDevice, gpuInputBuffers: GPUBuffer[]) => { 149 | // Bind group layout and bind group 150 | const layoutEntries: GPUBindGroupLayoutEntry[] = []; 151 | for (let i = 0; i < gpuInputBuffers.length; i++) { 152 | layoutEntries.push({ 153 | binding: i, 154 | visibility: GPUShaderStage.COMPUTE, 155 | buffer: { 156 | type: "read-only-storage" 157 | } 158 | }); 159 | } 160 | 161 | const resultLayoutEntry: GPUBindGroupLayoutEntry = { 162 | binding: gpuInputBuffers.length, 163 | visibility: GPUShaderStage.COMPUTE, 164 | buffer: { 165 | type: "storage" 166 | } 167 | }; 168 | 169 | layoutEntries.push(resultLayoutEntry); 170 | 171 | const layout = { entries: layoutEntries }; 172 | 173 | return device.createBindGroupLayout(layout); 174 | }; 175 | 176 | const createBindGroup = (device: GPUDevice, bindGroupLayout: GPUBindGroupLayout, gpuInputBuffers: GPUBuffer[], gpuOutputBuffer: GPUBuffer) => { 177 | const entriesToBind = gpuInputBuffers.map((gpuInputBuffer, i) => { 178 | return { 179 | binding: i, 180 | resource: { 181 | buffer: gpuInputBuffer 182 | } 183 | }; 184 | }); 185 | 186 | entriesToBind.push({ 187 | binding: gpuInputBuffers.length, 188 | resource: { 189 | buffer: gpuOutputBuffer 190 | } 191 | }); 192 | 193 | const bindGroup = device.createBindGroup({ 194 | layout: bindGroupLayout, 195 | entries: entriesToBind 196 | }); 197 | 198 | return bindGroup; 199 | }; -------------------------------------------------------------------------------- /src/gpu/entries/field/evalString.ts: -------------------------------------------------------------------------------- 1 | import { CurveType } from "../../curveSpecific"; 2 | import { gpuU32Inputs, gpuU32PuppeteerString } from "../../utils"; 3 | 4 | // used for evaluating in the puppeteer context 5 | export const fieldEntryEvaluationString = ( 6 | wgslFunction: string, 7 | curve: CurveType, 8 | inputs: gpuU32Inputs[] 9 | ) => { 10 | let evalString = `(field_entry)('${wgslFunction}', '${curve.toString()}', [`; 11 | for (let i = 0; i < inputs.length; i++) { 12 | evalString += `${gpuU32PuppeteerString(inputs[i])},` 13 | } 14 | evalString += '])'; 15 | return evalString; 16 | }; -------------------------------------------------------------------------------- /src/gpu/entries/field/fieldAdd.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntToU32Array, gpuU32Inputs, u32ArrayToBigInts } from '../../utils'; 4 | import { CurveType } from '../../curveSpecific'; 5 | import { fieldEntryEvaluationString } from './evalString'; 6 | import { FIELD_SIZE } from '../../U32Sizes'; 7 | 8 | describe('u256Add', () => { 9 | let browser: Browser; 10 | beforeAll(async () => { 11 | browser = await puppeteer.launch({ 12 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 13 | devtools: true, 14 | headless: false, 15 | args: ['#enable-webgpu-developer-features'] 16 | }); 17 | 18 | const page = (await browser.pages())[0]; 19 | await page.goto("http://localhost:4000"); 20 | }); 21 | 22 | afterAll(async () => { 23 | await browser.close(); 24 | }); 25 | 26 | it.each([ 27 | [BigInt(2), BigInt(4), BigInt(6)], 28 | // aleo field order 8444461749428370424248824938781546531375899335154063827935233455917409239041 29 | // -- basically, anything added to this should result in itself 30 | [BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239041'), BigInt(0), BigInt(0)], 31 | [BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239041'), BigInt(1), BigInt(1)], 32 | [BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239040'), BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239040'), BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239039')], 33 | [BigInt(0), BigInt(0), BigInt(0)], 34 | [BigInt(1), BigInt(8), BigInt(9)], 35 | [BigInt(4294967297), BigInt(4294967297), BigInt(8589934594)], 36 | [BigInt(3458380512), BigInt(3458380512), BigInt(6916761024)], 37 | [BigInt('14000000000000000000000000000000000000'), BigInt('14000000000000000000000000000000000000'), BigInt('28000000000000000000000000000000000000')], 38 | [BigInt('1684996666696914987166688442938726917102321526408785780068975640575'), BigInt('1684996666696914987166688442938726917102321526408785780068975640575'), BigInt('3369993333393829974333376885877453834204643052817571560137951281150')] 39 | ])('should add field numbers and reduce them if necessary', async (input1: bigint, input2: bigint, expected: bigint) => { 40 | // need to pass an untyped array here 41 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input1), individualInputSize: FIELD_SIZE }; 42 | const u32Input2: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input2), individualInputSize: FIELD_SIZE }; 43 | const evalString = fieldEntryEvaluationString('field_add', CurveType.BLS12_377, [u32Input1, u32Input2]); 44 | const result = await ((await browser.pages())[0]).evaluate(evalString); 45 | const arr = Object.values(result as object); 46 | const uint32ArrayResult = new Uint32Array(arr); 47 | const bigIntResult = u32ArrayToBigInts(uint32ArrayResult)[0]; 48 | 49 | expect(bigIntResult.toString()).toEqual(expected.toString()); 50 | }); 51 | }); -------------------------------------------------------------------------------- /src/gpu/entries/field/fieldEntry.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_SIZE } from "../../U32Sizes"; 2 | import { CurveType, getCurveParamsWGSL } from "../../curveSpecific"; 3 | import { gpuU32Inputs } from "../../utils"; 4 | import { FieldModulusWGSL } from "../../wgsl/FieldModulus"; 5 | import { U256WGSL } from "../../wgsl/U256"; 6 | import { batchedEntry } from "../entryCreator"; 7 | 8 | export const field_entry = async ( 9 | wgslFunction: string, 10 | curve: CurveType, 11 | inputs: gpuU32Inputs[], 12 | batchSize?: number 13 | ) => { 14 | let inputBindings = ''; 15 | let args = ''; 16 | for (let i = 0; i < inputs.length; i++) { 17 | inputBindings += `@group(0) @binding(${i})\n 18 | var input${i}: array;\n`; 19 | args += `input${i}[global_id.x],` 20 | } 21 | // drop end comma from args 22 | args.slice(0, -1); 23 | const outputBindings = `@group(0) @binding(${inputs.length})\n 24 | var output: array;\n`; 25 | 26 | const shaderEntry = ` 27 | ${inputBindings} 28 | ${outputBindings} 29 | 30 | @compute @workgroup_size(64) 31 | fn main( 32 | @builtin(global_invocation_id) 33 | global_id : vec3 34 | ) { 35 | var result = ${wgslFunction}(${args}); 36 | output[global_id.x] = result; 37 | } 38 | `; 39 | 40 | const curveParamsWGSL = getCurveParamsWGSL(curve); 41 | 42 | const shaderModules = [U256WGSL, curveParamsWGSL, FieldModulusWGSL, shaderEntry]; 43 | 44 | return await batchedEntry(inputs, shaderModules.join(''), FIELD_SIZE, batchSize); 45 | } 46 | 47 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 48 | (window as any).field_entry = field_entry; -------------------------------------------------------------------------------- /src/gpu/entries/field/fieldModulusExponentiation.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntToU32Array, gpuU32Inputs, u32ArrayToBigInts } from '../../utils'; 4 | import { CurveType } from '../../curveSpecific'; 5 | import { fieldEntryEvaluationString } from './evalString'; 6 | import { FIELD_SIZE } from '../../U32Sizes'; 7 | 8 | describe('fieldModulusExponentiation', () => { 9 | let browser: Browser; 10 | beforeAll(async () => { 11 | browser = await puppeteer.launch({ 12 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 13 | devtools: true, 14 | headless: false, 15 | args: ['#enable-webgpu-developer-features'] 16 | }); 17 | 18 | const page = (await browser.pages())[0]; 19 | await page.goto("http://localhost:4000"); 20 | }); 21 | 22 | afterAll(async () => { 23 | await browser.close(); 24 | }); 25 | 26 | it.each([ 27 | [BigInt(2), BigInt(4), BigInt(16)], 28 | [BigInt(1), BigInt(0), BigInt(1)], 29 | [BigInt(9), BigInt(8), BigInt('43046721')], 30 | [BigInt(15000), BigInt(2), BigInt('225000000')], 31 | [BigInt(25), BigInt('60001509534603559531609739528203892656505753216962260608619555'), BigInt('2162778637144600773838902968767688614233520848441049636087321895557933046729')], 32 | [BigInt(25), BigInt('30000754767301779765804869764101946328252876608481130304309778'), BigInt('2774790624473817237320672554055090979281590950368967309207911707712866578499')] 33 | ])('should do field exponentiation', async (input1: bigint, input2: bigint, expected: bigint) => { 34 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input1), individualInputSize: FIELD_SIZE }; 35 | const u32Input2: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input2), individualInputSize: FIELD_SIZE }; 36 | const evalString = fieldEntryEvaluationString('field_pow', CurveType.BLS12_377, [u32Input1, u32Input2]); 37 | const result = await ((await browser.pages())[0]).evaluate(evalString); 38 | const arr = Object.values(result as object); 39 | const uint32ArrayResult = new Uint32Array(arr); 40 | const bigIntResult = u32ArrayToBigInts(uint32ArrayResult)[0]; 41 | 42 | expect(bigIntResult.toString()).toEqual(expected.toString()); 43 | }); 44 | }); -------------------------------------------------------------------------------- /src/gpu/entries/field/fieldModulusFieldInverse.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntToU32Array, gpuU32Inputs, u32ArrayToBigInts } from '../../utils'; 4 | import { CurveType } from '../../curveSpecific'; 5 | import { fieldEntryEvaluationString } from './evalString'; 6 | import { FIELD_SIZE } from '../../U32Sizes'; 7 | 8 | describe('fieldInverse', () => { 9 | let browser: Browser; 10 | beforeAll(async () => { 11 | browser = await puppeteer.launch({ 12 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 13 | devtools: true, 14 | headless: false, 15 | args: ['#enable-webgpu-developer-features'] 16 | }); 17 | 18 | const page = (await browser.pages())[0]; 19 | await page.goto("http://localhost:4000"); 20 | }); 21 | 22 | afterAll(async () => { 23 | await browser.close(); 24 | }); 25 | 26 | it.each([ 27 | [BigInt('123'), BigInt('7140032698703662797738843850677079994008890494764411691912717718824476104555')], 28 | [BigInt('7140032698703662797738843850677079994008890494764411691912717718824476104555'), BigInt('123')], 29 | [BigInt('9823798737'), BigInt('7721785495925626920722707159254091218258581972178005996743736554530544917209')], 30 | ])('should add invert input field elements', async (input1: bigint, expected: bigint) => { 31 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input1), individualInputSize: FIELD_SIZE }; 32 | const evalString = fieldEntryEvaluationString('field_inverse', CurveType.BLS12_377, [u32Input1]); 33 | const result = await ((await browser.pages())[0]).evaluate(evalString); 34 | const arr = Object.values(result as object); 35 | const uint32ArrayResult = new Uint32Array(arr); 36 | const bigIntResult = u32ArrayToBigInts(uint32ArrayResult)[0]; 37 | 38 | expect(bigIntResult.toString()).toEqual(expected.toString()); 39 | }); 40 | }); -------------------------------------------------------------------------------- /src/gpu/entries/field/fieldModulusFieldMultiply.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntToU32Array, gpuU32Inputs, u32ArrayToBigInts } from '../../utils'; 4 | import { FIELD_MODULUS } from '../../../params/BLS12_377Constants'; 5 | import { fieldEntryEvaluationString } from './evalString'; 6 | import { CurveType } from '../../curveSpecific'; 7 | import { FIELD_SIZE } from '../../U32Sizes'; 8 | 9 | describe('u256Multiply', () => { 10 | let browser: Browser; 11 | beforeAll(async () => { 12 | browser = await puppeteer.launch({ 13 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 14 | devtools: true, 15 | headless: false, 16 | args: ['#enable-webgpu-developer-features'] 17 | }); 18 | 19 | const page = (await browser.pages())[0]; 20 | await page.goto("http://localhost:4000"); 21 | }); 22 | 23 | afterAll(async () => { 24 | await browser.close(); 25 | }); 26 | 27 | it.each([ 28 | [BigInt(2), BigInt(4), BigInt(8)], 29 | [BigInt('17684216722439012352'), BigInt('13072530704011624448'), BigInt('231177466080479803996665978540257181696')], 30 | [(FIELD_MODULUS / BigInt(2)), BigInt(2), FIELD_MODULUS - BigInt(1)], 31 | [FIELD_MODULUS, BigInt(0), BigInt(0)], 32 | [FIELD_MODULUS, BigInt(2), BigInt(0)], 33 | [FIELD_MODULUS, FIELD_MODULUS, BigInt(0)], 34 | [FIELD_MODULUS + BigInt(2), BigInt(2), BigInt(4)], 35 | [BigInt('542101086242752217003726400434970855712890625'), BigInt('542101086242752217003726400434970855712890625'), BigInt('4049876799198613235662808409310909745689267379541545003864313481035049862036')] 36 | ])('should multiply uint256 field numbers', async (input1: bigint, input2: bigint, expected: bigint) => { 37 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input1), individualInputSize: FIELD_SIZE }; 38 | const u32Input2: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input2), individualInputSize: FIELD_SIZE }; 39 | const evalString = fieldEntryEvaluationString('field_multiply', CurveType.BLS12_377, [u32Input1, u32Input2]); 40 | const result = await ((await browser.pages())[0]).evaluate(evalString); 41 | const arr = Object.values(result as object); 42 | const uint32ArrayResult = new Uint32Array(arr); 43 | const bigIntResult = u32ArrayToBigInts(uint32ArrayResult)[0]; 44 | 45 | expect(bigIntResult.toString()).toEqual(expected.toString()); 46 | }); 47 | }); -------------------------------------------------------------------------------- /src/gpu/entries/field/fieldModulusFieldReduce.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntToU32Array, gpuU32Inputs, u32ArrayToBigInts } from '../../utils'; 4 | import { FIELD_MODULUS } from '../../../params/BLS12_377Constants'; 5 | import { fieldEntryEvaluationString } from './evalString'; 6 | import { CurveType } from '../../curveSpecific'; 7 | import { FIELD_SIZE } from '../../U32Sizes'; 8 | 9 | describe('fieldModulusReduce', () => { 10 | let browser: Browser; 11 | beforeAll(async () => { 12 | browser = await puppeteer.launch({ 13 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 14 | devtools: true, 15 | headless: false, 16 | args: ['#enable-webgpu-developer-features'] 17 | }); 18 | 19 | const page = (await browser.pages())[0]; 20 | await page.goto("http://localhost:4000"); 21 | }); 22 | 23 | afterAll(async () => { 24 | await browser.close(); 25 | }); 26 | 27 | it.each([ 28 | [BigInt(2), BigInt(2)], 29 | [FIELD_MODULUS, BigInt(0)], 30 | [BigInt('16888923498856740848497649877563093062751798670308127655870466911834818478081'), BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239040')], 31 | [BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239042'), BigInt(1)], 32 | [BigInt('120398457102983457029138745023987'), BigInt('120398457102983457029138745023987')] 33 | ])('should add reduce big ints into field elements', async (input1: bigint, expected: bigint) => { 34 | // need to pass an untyped array here 35 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input1), individualInputSize: FIELD_SIZE }; 36 | const evalString = fieldEntryEvaluationString('field_reduce', CurveType.BLS12_377, [u32Input1]); 37 | const result = await ((await browser.pages())[0]).evaluate(evalString); 38 | const arr = Object.values(result as object); 39 | const uint32ArrayResult = new Uint32Array(arr); 40 | const bigIntResult = u32ArrayToBigInts(uint32ArrayResult)[0]; 41 | 42 | expect(bigIntResult.toString()).toEqual(expected.toString()); 43 | }); 44 | }); -------------------------------------------------------------------------------- /src/gpu/entries/field/fieldSqrt.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntToU32Array, gpuU32Inputs, u32ArrayToBigInts } from '../../utils'; 4 | import { fieldEntryEvaluationString } from './evalString'; 5 | import { CurveType } from '../../curveSpecific'; 6 | import { FIELD_SIZE } from '../../U32Sizes'; 7 | 8 | describe('fieldSqrt', () => { 9 | let browser: Browser; 10 | beforeAll(async () => { 11 | browser = await puppeteer.launch({ 12 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 13 | devtools: true, 14 | headless: false, 15 | args: ['#enable-webgpu-developer-features'] 16 | }); 17 | 18 | const page = (await browser.pages())[0]; 19 | await page.goto("http://localhost:4000"); 20 | }); 21 | 22 | afterAll(async () => { 23 | await browser.close(); 24 | }); 25 | 26 | it.each([ 27 | [BigInt(4), BigInt(2)], 28 | [BigInt(9), BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239038')], 29 | [BigInt(25), BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239036')], 30 | [BigInt('9657672915538583998542678820329009'), BigInt('8444461749428370424248824938781546531375899335154063827935135182457535585544')] 31 | ])('should find the square root of numbers', async (input1: bigint, expected: bigint) => { 32 | // need to pass an untyped array here 33 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input1), individualInputSize: FIELD_SIZE }; 34 | const evalString = fieldEntryEvaluationString('field_sqrt', CurveType.BLS12_377, [u32Input1]); 35 | const result = await ((await browser.pages())[0]).evaluate(evalString); 36 | const arr = Object.values(result as object); 37 | const uint32ArrayResult = new Uint32Array(arr); 38 | const bigIntResult = u32ArrayToBigInts(uint32ArrayResult)[0]; 39 | 40 | expect(bigIntResult.toString()).toEqual(expected.toString()); 41 | }); 42 | }); -------------------------------------------------------------------------------- /src/gpu/entries/field/fieldSub.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntToU32Array, gpuU32Inputs, u32ArrayToBigInts } from '../../utils'; 4 | import { FIELD_MODULUS } from '../../../params/BLS12_377Constants'; 5 | import { CurveType } from '../../curveSpecific'; 6 | import { FIELD_SIZE } from '../../U32Sizes'; 7 | import { fieldEntryEvaluationString } from './evalString'; 8 | 9 | describe('fieldSub', () => { 10 | let browser: Browser; 11 | beforeAll(async () => { 12 | browser = await puppeteer.launch({ 13 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 14 | devtools: true, 15 | headless: false, 16 | args: ['#enable-webgpu-developer-features'] 17 | }); 18 | 19 | const page = (await browser.pages())[0]; 20 | await page.goto("http://localhost:4000"); 21 | }); 22 | 23 | afterAll(async () => { 24 | await browser.close(); 25 | }); 26 | 27 | it.each([ 28 | // should wrap back around 29 | [BigInt(2), BigInt(4), BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239039')], 30 | // anything minus aleo field order should result in itself 31 | [BigInt(0), FIELD_MODULUS, BigInt(0)], 32 | [BigInt(1), FIELD_MODULUS, BigInt(1)], 33 | [BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239040'), BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239040'), BigInt(0)], 34 | [BigInt(0), BigInt(0), BigInt(0)], 35 | [BigInt(9), BigInt(8), BigInt(1)], 36 | [BigInt(4294967296), BigInt(4294967297), BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239040')], 37 | [BigInt(3458380512), BigInt(3458380512), BigInt(0)], 38 | [BigInt('28000000000000000000000000000000000000'), BigInt('14000000000000000000000000000000000000'), BigInt('14000000000000000000000000000000000000')], 39 | ])('should sub field numbers and wrap them if necessary', async (input1: bigint, input2: bigint, expected: bigint) => { 40 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input1), individualInputSize: FIELD_SIZE }; 41 | const u32Input2: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input2), individualInputSize: FIELD_SIZE }; 42 | const evalString = fieldEntryEvaluationString('field_sub', CurveType.BLS12_377, [u32Input1, u32Input2]); 43 | const result = await ((await browser.pages())[0]).evaluate(evalString); 44 | const arr = Object.values(result as object); 45 | const uint32ArrayResult = new Uint32Array(arr); 46 | const bigIntResult = u32ArrayToBigInts(uint32ArrayResult)[0]; 47 | 48 | expect(bigIntResult.toString()).toEqual(expected.toString()); 49 | }); 50 | }); -------------------------------------------------------------------------------- /src/gpu/entries/multipassEntryCreator.ts: -------------------------------------------------------------------------------- 1 | import { workgroupSize } from "../curveSpecific"; 2 | 3 | /** 4 | * Creates and executes multipass pipeline. 5 | * Assumes that the result buffer of each pass is the input buffer of the next pass. 6 | * 7 | * @param gpu Device to run passes on 8 | * @param passes Code to run on each pass. Order of list is respected. 9 | */ 10 | export const multipassEntryCreator = async (passes: IGPUExecution[], entryInfo: IEntryInfo): Promise => { 11 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 12 | const gpu = (await getDevice())!; 13 | 14 | const commandEncoder = gpu.createCommandEncoder(); 15 | 16 | const allBuffers: GPUBuffer[] = []; 17 | 18 | let previousResultBuffers: GPUBuffer[] | undefined; 19 | for (let i = 0; i < passes.length; i++) { 20 | const execution = passes[i]; 21 | 22 | const inputData = execution.gpuInput; 23 | const resultData = execution.gpuOutput; 24 | const shaderModule = gpu.createShaderModule({ code: execution.shader.code }); 25 | 26 | // Create input buffers 27 | const inputBuffers: GPUBuffer[] = []; 28 | for (let j = 0; j < inputData.inputBufferTypes.length; j++) { 29 | const mappedInput = inputData.mappedInputs?.get(j); 30 | if (mappedInput) { 31 | const inputBuffer = gpu.createBuffer({ 32 | mappedAtCreation: true, 33 | size: inputData.inputBufferSizes[j], 34 | usage: inputData.inputBufferUsages[j] 35 | }); 36 | const arrayBufferInput = inputBuffer.getMappedRange(); 37 | new Uint32Array(arrayBufferInput).set(mappedInput); 38 | inputBuffer.unmap(); 39 | inputBuffers.push(inputBuffer); 40 | } else { 41 | const inputBuffer = gpu.createBuffer({ 42 | size: inputData.inputBufferSizes[j], 43 | usage: inputData.inputBufferUsages[j] 44 | }); 45 | inputBuffers.push(inputBuffer); 46 | } 47 | } 48 | 49 | // Create result buffers 50 | const resultBuffers: GPUBuffer[] = []; 51 | for (let i = 0; i < resultData.resultBufferTypes.length; i++) { 52 | const resultBuffer = gpu.createBuffer({ 53 | size: resultData.resultBufferSizes[i], 54 | usage: resultData.resultBufferUsages[i] 55 | }); 56 | resultBuffers.push(resultBuffer); 57 | } 58 | 59 | // Create bind group layout 60 | const bindGroupLayout = createBindGroupLayout(gpu, inputBuffers, resultBuffers, inputData.inputBufferTypes); 61 | 62 | // Create bind group 63 | const bindGroup = createBindGroup(gpu, bindGroupLayout, inputBuffers, resultBuffers); 64 | 65 | // Create pipeline 66 | const pipeline = gpu.createComputePipeline({ 67 | layout: gpu.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }), 68 | compute: { 69 | module: shaderModule, 70 | entryPoint: execution.shader.entryPoint 71 | } 72 | }); 73 | 74 | // Copy previous result buffer to input buffer 75 | if (previousResultBuffers != undefined) { 76 | for (let i = 0; i < previousResultBuffers.length; i++) { 77 | commandEncoder.copyBufferToBuffer( 78 | previousResultBuffers[i], 79 | 0, 80 | inputBuffers[i], 81 | 0, 82 | inputData.inputBufferSizes[i] 83 | ); 84 | } 85 | } 86 | 87 | // Run compute pass 88 | const passEncoder = commandEncoder.beginComputePass(); 89 | passEncoder.setPipeline(pipeline); 90 | passEncoder.setBindGroup(0, bindGroup); 91 | passEncoder.dispatchWorkgroups(Math.ceil((entryInfo.numInputsForWorkgroup ?? entryInfo.numInputs) / workgroupSize)); 92 | passEncoder.end(); 93 | 94 | previousResultBuffers = resultBuffers; 95 | allBuffers.push(...inputBuffers); 96 | allBuffers.push(...resultBuffers); 97 | } 98 | 99 | // Create buffer to read result 100 | const gpuReadBuffer = gpu.createBuffer({ 101 | size: entryInfo.outputSize, 102 | usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ 103 | }); 104 | allBuffers.push(gpuReadBuffer); 105 | 106 | if (previousResultBuffers) { 107 | commandEncoder.copyBufferToBuffer( 108 | previousResultBuffers[0], 109 | 0, 110 | gpuReadBuffer, 111 | 0, 112 | entryInfo.outputSize 113 | ); 114 | } 115 | 116 | const gpuCommands = commandEncoder.finish(); 117 | gpu.queue.submit([gpuCommands]); 118 | 119 | await gpuReadBuffer.mapAsync(GPUMapMode.READ); 120 | const arrayBuffer = gpuReadBuffer.getMappedRange(); 121 | const result = new Uint32Array(arrayBuffer.slice(0)); 122 | gpuReadBuffer.unmap(); 123 | 124 | // Destroy all buffers 125 | for (const buffer of allBuffers) { 126 | buffer.destroy(); 127 | } 128 | gpu.destroy(); 129 | return result; 130 | } 131 | 132 | /** 133 | * Description of gpu inputs. 134 | * 135 | * Expected that inputTypes and inputSizes are the same length. 136 | * mappedInputs should be a map of input index to Uint32Array. 137 | */ 138 | export interface IGPUInput { 139 | inputBufferTypes: GPUBufferBindingType[]; 140 | inputBufferSizes: number[]; 141 | inputBufferUsages: number[]; 142 | mappedInputs?: Map; 143 | } 144 | 145 | /** 146 | * Descriptior of gpu result buffers 147 | * 148 | * Expected that resultBufferTypes and resultBufferSizes are the same length. 149 | */ 150 | export interface IGPUResult { 151 | resultBufferTypes: GPUBufferBindingType[]; 152 | resultBufferSizes: number[]; 153 | resultBufferUsages: number[]; 154 | } 155 | 156 | export interface IShaderCode { 157 | code: string; 158 | entryPoint: string; 159 | } 160 | 161 | interface IGPUExecution { 162 | shader: IShaderCode; 163 | gpuInput: IGPUInput; 164 | gpuOutput: IGPUResult; 165 | } 166 | 167 | export class GPUExecution implements IGPUExecution { 168 | shader: IShaderCode; 169 | gpuInput: IGPUInput; 170 | gpuOutput: IGPUResult; 171 | 172 | 173 | constructor(shader: IShaderCode, gpuInput: IGPUInput, gpuOutput: IGPUResult) { 174 | this.shader = shader; 175 | this.gpuInput = gpuInput; 176 | this.gpuOutput = gpuOutput; 177 | } 178 | } 179 | 180 | export interface IEntryInfo { 181 | numInputs: number; 182 | outputSize: number; 183 | numInputsForWorkgroup?: number; 184 | } 185 | 186 | // Currently has the assumption that input buffers are in order of binding 187 | // Also assumes that the result buffer will always be of type "storage" 188 | const createBindGroupLayout = (device: GPUDevice, gpuInputBuffers: GPUBuffer[], gpuResultBuffers: GPUBuffer[], types: GPUBufferBindingType[]) => { 189 | // Bind group layout and bind group 190 | const layoutEntries: GPUBindGroupLayoutEntry[] = []; 191 | for (let i = 0; i < gpuInputBuffers.length; i++) { 192 | layoutEntries.push({ 193 | binding: i, 194 | visibility: GPUShaderStage.COMPUTE, 195 | buffer: { 196 | type: types[i] 197 | } 198 | }); 199 | } 200 | 201 | for (let i = 0; i < gpuResultBuffers.length; i++) { 202 | layoutEntries.push({ 203 | binding: i + gpuInputBuffers.length, 204 | visibility: GPUShaderStage.COMPUTE, 205 | buffer: { 206 | type: "storage" 207 | } 208 | }); 209 | } 210 | 211 | const layout = { entries: layoutEntries }; 212 | 213 | return device.createBindGroupLayout(layout); 214 | }; 215 | 216 | const createBindGroup = (device: GPUDevice, bindGroupLayout: GPUBindGroupLayout, gpuInputBuffers: GPUBuffer[], gpuOutputBuffers: GPUBuffer[]) => { 217 | const inputEntriesToBind = gpuInputBuffers.map((gpuInputBuffer, i) => { 218 | return { 219 | binding: i, 220 | resource: { 221 | buffer: gpuInputBuffer 222 | } 223 | }; 224 | }); 225 | 226 | const resultEntriesToBind = gpuOutputBuffers.map((gpuOutputBuffer, i) => { 227 | return { 228 | binding: i + gpuInputBuffers.length, 229 | resource: { 230 | buffer: gpuOutputBuffer 231 | } 232 | } 233 | }); 234 | 235 | const entriesToBind = inputEntriesToBind.concat(resultEntriesToBind); 236 | 237 | const bindGroup = device.createBindGroup({ 238 | layout: bindGroupLayout, 239 | entries: entriesToBind 240 | }); 241 | 242 | return bindGroup; 243 | }; 244 | 245 | export const getDevice = async () => { 246 | if (!("gpu" in navigator)) { 247 | console.log("WebGPU is not supported on this device"); 248 | return; 249 | } 250 | 251 | const adapter = await navigator.gpu.requestAdapter({powerPreference: "high-performance"}); 252 | if (!adapter) { 253 | console.log("Adapter not found"); 254 | return; 255 | } 256 | return await adapter.requestDevice(); 257 | } -------------------------------------------------------------------------------- /src/gpu/entries/multipassEntryCreatorBufferReuse.ts: -------------------------------------------------------------------------------- 1 | import { workgroupSize } from "../curveSpecific"; 2 | 3 | /** 4 | * Creates and executes multipass pipeline. 5 | * Assumes that the result buffer of each pass is the input buffer of the next pass. 6 | * 7 | * @param gpu Device to run passes on 8 | * @param passes Code to run on each pass. Order of list is respected. 9 | */ 10 | export const multipassEntryCreatorReuseBuffers = async (gpu: GPUDevice, passes: IGPUExecution[], entryInfo: IEntryInfo): Promise => { 11 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 12 | const commandEncoder = gpu.createCommandEncoder(); 13 | 14 | // Run the passes 15 | for (let passIndex = 0; passIndex < passes.length; passIndex++) { 16 | const execution = passes[passIndex]; 17 | 18 | const inputData = execution.gpuInput; 19 | const resultData = execution.gpuOutput; 20 | const shaderModule = gpu.createShaderModule({ code: execution.shader.code }); 21 | 22 | for (let i = 0; i < inputData.inputBuffers.length; i++) { 23 | const mappedInput = inputData.mappedInputs?.get(i); 24 | const inputBuffer = inputData.inputBuffers[i]; 25 | if (mappedInput) { 26 | gpu.queue.writeBuffer(inputBuffer, 0, mappedInput, 0); 27 | } 28 | } 29 | 30 | // Create bind group layout 31 | const bindGroupLayout = createBindGroupLayout( 32 | gpu, 33 | inputData.inputBuffers, 34 | resultData.resultBuffers 35 | ); 36 | 37 | // Create bind group 38 | const bindGroup = createBindGroup( 39 | gpu, 40 | bindGroupLayout, 41 | inputData.inputBuffers, 42 | resultData.resultBuffers 43 | ); 44 | 45 | // Create pipeline 46 | const pipeline = gpu.createComputePipeline({ 47 | layout: gpu.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }), 48 | compute: { 49 | module: shaderModule, 50 | entryPoint: execution.shader.entryPoint 51 | } 52 | }); 53 | 54 | // Run compute pass 55 | const passEncoder = commandEncoder.beginComputePass(); 56 | passEncoder.setPipeline(pipeline); 57 | passEncoder.setBindGroup(0, bindGroup); 58 | passEncoder.dispatchWorkgroups(Math.ceil(entryInfo.numInputs / workgroupSize)); 59 | passEncoder.end(); 60 | 61 | // previousResultBuffers = resultBuffers; 62 | } 63 | 64 | // Create buffer to read result 65 | const gpuReadBuffer = gpu.createBuffer({ 66 | size: entryInfo.outputSize, 67 | usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ 68 | }); 69 | 70 | const finalResultBuffer = passes[passes.length - 1].gpuOutput.resultBuffers[0]; 71 | 72 | // if (previousResultBuffers) { 73 | commandEncoder.copyBufferToBuffer( 74 | finalResultBuffer, 75 | 0, 76 | gpuReadBuffer, 77 | 0, 78 | entryInfo.outputSize 79 | ); 80 | // } 81 | 82 | const gpuCommands = commandEncoder.finish(); 83 | gpu.queue.submit([gpuCommands]); 84 | 85 | await gpuReadBuffer.mapAsync(GPUMapMode.READ); 86 | const arrayBuffer = gpuReadBuffer.getMappedRange(); 87 | const result = new Uint32Array(arrayBuffer.slice(0)); 88 | gpuReadBuffer.unmap(); 89 | 90 | return result; 91 | } 92 | 93 | /** 94 | * Description of gpu inputs. 95 | * 96 | * Expected that inputTypes and inputSizes are the same length. 97 | * mappedInputs should be a map of input index to Uint32Array. 98 | */ 99 | export interface IGPUInput { 100 | inputBuffers: GPUBuffer[]; 101 | mappedInputs?: Map; 102 | } 103 | 104 | /** 105 | * Descriptior of gpu result buffers 106 | * 107 | * Expected that resultBufferTypes and resultBufferSizes are the same length. 108 | */ 109 | export interface IGPUResult { 110 | resultBuffers: GPUBuffer[] 111 | } 112 | 113 | export interface IShaderCode { 114 | code: string; 115 | entryPoint: string; 116 | } 117 | 118 | interface IGPUExecution { 119 | shader: IShaderCode; 120 | gpuInput: IGPUInput; 121 | gpuOutput: IGPUResult; 122 | } 123 | 124 | export class GPUExecution implements IGPUExecution { 125 | shader: IShaderCode; 126 | gpuInput: IGPUInput; 127 | gpuOutput: IGPUResult; 128 | 129 | 130 | constructor(shader: IShaderCode, gpuInput: IGPUInput, gpuOutput: IGPUResult) { 131 | this.shader = shader; 132 | this.gpuInput = gpuInput; 133 | this.gpuOutput = gpuOutput; 134 | } 135 | } 136 | 137 | export interface IEntryInfo { 138 | numInputs: number; 139 | outputSize: number; 140 | } 141 | 142 | // Currently has the assumption that input buffers are in order of binding 143 | // Also assumes that the result buffer will always be of type "storage" 144 | const createBindGroupLayout = (device: GPUDevice, gpuInputBuffers: GPUBuffer[], gpuResultBuffers: GPUBuffer[]) => { 145 | // Bind group layout and bind group 146 | const layoutEntries: GPUBindGroupLayoutEntry[] = []; 147 | for (let i = 0; i < gpuInputBuffers.length; i++) { 148 | layoutEntries.push({ 149 | binding: i, 150 | visibility: GPUShaderStage.COMPUTE, 151 | buffer: { 152 | type: "storage" 153 | } 154 | }); 155 | } 156 | 157 | for (let i = 0; i < gpuResultBuffers.length; i++) { 158 | layoutEntries.push({ 159 | binding: i + gpuInputBuffers.length, 160 | visibility: GPUShaderStage.COMPUTE, 161 | buffer: { 162 | type: "storage" 163 | } 164 | }); 165 | } 166 | 167 | const layout = { entries: layoutEntries }; 168 | 169 | return device.createBindGroupLayout(layout); 170 | }; 171 | 172 | const createBindGroup = (device: GPUDevice, bindGroupLayout: GPUBindGroupLayout, gpuInputBuffers: GPUBuffer[], gpuOutputBuffers: GPUBuffer[]) => { 173 | const inputEntriesToBind = gpuInputBuffers.map((gpuInputBuffer, i) => { 174 | return { 175 | binding: i, 176 | resource: { 177 | buffer: gpuInputBuffer 178 | } 179 | }; 180 | }); 181 | 182 | const resultEntriesToBind = gpuOutputBuffers.map((gpuOutputBuffer, i) => { 183 | return { 184 | binding: i + gpuInputBuffers.length, 185 | resource: { 186 | buffer: gpuOutputBuffer 187 | } 188 | } 189 | }); 190 | 191 | const entriesToBind = inputEntriesToBind.concat(resultEntriesToBind); 192 | 193 | const bindGroup = device.createBindGroup({ 194 | layout: bindGroupLayout, 195 | entries: entriesToBind 196 | }); 197 | 198 | return bindGroup; 199 | }; -------------------------------------------------------------------------------- /src/gpu/entries/pippengerMSMEntry.test.ts: -------------------------------------------------------------------------------- 1 | // import puppeteer from 'puppeteer'; 2 | // import { Browser } from 'puppeteer'; 3 | // import { bigIntsToU16Array, u32ArrayToBigInts } from '../utils'; 4 | // import { FieldMath } from '../../utils/BLS12_377FieldMath'; 5 | 6 | describe('dummyTest', () => { 7 | it('should pass', () => { 8 | expect(true).toBe(true); 9 | }); 10 | }); 11 | 12 | // describe('pippengerMSM', () => { 13 | // let browser: Browser; 14 | // beforeAll(async () => { 15 | // browser = await puppeteer.launch({ 16 | // // Might need to be configurable at some point. 17 | // // '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome' 18 | // //executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 19 | // executablePath: '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome', 20 | // devtools: true, 21 | // headless: false, 22 | // args: ['#enable-webgpu-developer-features'] 23 | // }); 24 | 25 | // const page = (await browser.pages())[0]; 26 | // await page.goto("http://localhost:4000"); 27 | // }); 28 | 29 | // afterAll(async () => { 30 | // await browser.close(); 31 | // }); 32 | 33 | // it.each([ 34 | // [ 35 | // [BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246')], 36 | // [BigInt('4115981352351835515120931692070964483678586420443690054992391095360224208994')], 37 | // BigInt('2003769298882165992553856497540267294464327539507494098187236363544388186784') 38 | // ], 39 | // [ 40 | // [BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246'), 41 | // BigInt('2796670805570508460920584878396618987767121022598342527208237783066948667246')], 42 | // [BigInt('4815997971641423936781886037889355281829129259429609070232507389609965623593'), 43 | // BigInt('2592192440869895559682132736176579095549249684480407380090163054781396615195')], 44 | // BigInt('3286038430103828453403177968732741410787578586833327588879065467465127482803') 45 | // ] 46 | // ])('should compute MSM', async ( 47 | // affinePointXCoords: bigint[], 48 | // scalars: bigint[], 49 | // expectedAffinePointXCoord: bigint, 50 | // ) => { 51 | // const fieldMath = new FieldMath(); 52 | // const extendedPoints = []; 53 | // for (let i = 0; i < affinePointXCoords.length; i++) { 54 | // extendedPoints.push(fieldMath.getPointFromX(affinePointXCoords[i])); 55 | // } 56 | // const scalarsAsU16s = Array.from(bigIntsToU16Array(scalars)); 57 | 58 | // const result = await ((await browser.pages())[0]).evaluate(`(pippenger_msm)([${extendedPoints}], [${scalarsAsU16s}], [${fieldMath}])`); 59 | // const arr = Object.values(result as object); 60 | // const uint32ArrayResult = new Uint32Array(arr); 61 | // const bigIntsResult = u32ArrayToBigInts(uint32ArrayResult); 62 | 63 | // expect(bigIntsResult[0].toString()).toEqual(expectedAffinePointXCoord.toString()); 64 | // }); 65 | // }); 66 | 67 | export {}; -------------------------------------------------------------------------------- /src/gpu/entries/u256/evalString.ts: -------------------------------------------------------------------------------- 1 | import { gpuU32Inputs, gpuU32PuppeteerString } from "../../utils"; 2 | 3 | // used for evaluating in the puppeteer context 4 | export const u256EntryEvaluationString = ( 5 | wgslFunction: string, 6 | inputs: gpuU32Inputs[] 7 | ) => { 8 | let evalString = `(u256_entry)('${wgslFunction}', [`; 9 | for (let i = 0; i < inputs.length; i++) { 10 | evalString += `${gpuU32PuppeteerString(inputs[i])},` 11 | } 12 | evalString += '])'; 13 | return evalString; 14 | }; -------------------------------------------------------------------------------- /src/gpu/entries/u256/u256Add.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntToU32Array, gpuU32Inputs, u32ArrayToBigInts } from '../../utils'; 4 | import { u256EntryEvaluationString } from './evalString'; 5 | import { U256_SIZE } from '../../U32Sizes'; 6 | 7 | describe('u256Add', () => { 8 | let browser: Browser; 9 | beforeAll(async () => { 10 | browser = await puppeteer.launch({ 11 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 12 | devtools: true, 13 | headless: false, 14 | args: ['#enable-webgpu-developer-features'] 15 | }); 16 | 17 | const page = (await browser.pages())[0]; 18 | await page.goto("http://localhost:4000"); 19 | }); 20 | 21 | afterAll(async () => { 22 | await browser.close(); 23 | }); 24 | 25 | it.each([ 26 | [BigInt(2), BigInt(4), BigInt(6)], 27 | [BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239041'), BigInt(1), BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239042')], 28 | [BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239041'), BigInt('107347627487887824999322160069906361321894085330486500211522350551995720400894'), BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935')], 29 | [BigInt(0), BigInt(0), BigInt(0)], 30 | [BigInt(1), BigInt(8), BigInt(9)], 31 | [BigInt(4294967297), BigInt(4294967297), BigInt(8589934594)], 32 | [BigInt(3458380512), BigInt(3458380512), BigInt(6916761024)], 33 | [BigInt('14000000000000000000000000000000000000'), BigInt('14000000000000000000000000000000000000'), BigInt('28000000000000000000000000000000000000')], 34 | [BigInt('1684996666696914987166688442938726917102321526408785780068975640575'), BigInt('1684996666696914987166688442938726917102321526408785780068975640575'), BigInt('3369993333393829974333376885877453834204643052817571560137951281150')], 35 | [BigInt('18446744069414584321'), BigInt('4294967295'), BigInt('18446744073709551616')] 36 | ])('should add uint256 numbers', async (input1: bigint, input2: bigint, expected: bigint) => { 37 | // need to pass an untyped array here 38 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input1), individualInputSize: U256_SIZE }; 39 | const u32Input2: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input2), individualInputSize: U256_SIZE }; 40 | const evalString = u256EntryEvaluationString('u256_add', [u32Input1, u32Input2]); 41 | const result = await ((await browser.pages())[0]).evaluate(evalString); 42 | const arr = Object.values(result as object); 43 | const uint32ArrayResult = new Uint32Array(arr); 44 | const bigIntResult = u32ArrayToBigInts(uint32ArrayResult)[0]; 45 | 46 | expect(bigIntResult.toString()).toEqual(expected.toString()); 47 | }); 48 | }); -------------------------------------------------------------------------------- /src/gpu/entries/u256/u256Double.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from "puppeteer"; 2 | import { Browser } from "puppeteer"; 3 | import { bigIntToU32Array, u32ArrayToBigInts } from "../../utils"; 4 | import { u256EntryEvaluationString } from "./evalString"; 5 | import { FIELD_SIZE } from "../../U32Sizes"; 6 | 7 | describe("u256Double", () => { 8 | let browser: Browser; 9 | beforeAll(async () => { 10 | browser = await puppeteer.launch({ 11 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 12 | devtools: true, 13 | headless: false, 14 | args: ['#enable-webgpu-developer-features'] 15 | }); 16 | 17 | const page = (await browser.pages())[0]; 18 | await page.goto("http://localhost:4000"); 19 | }); 20 | 21 | afterAll(async () => { 22 | await browser.close(); 23 | }); 24 | 25 | it.each([ 26 | [BigInt(2), BigInt(4)], 27 | [BigInt(0), BigInt(0)], 28 | [BigInt(1), BigInt(2)], 29 | [BigInt(2147483648), BigInt(4294967296)], 30 | [BigInt('9223372036854775808'), BigInt('18446744073709551616')], 31 | [BigInt('57896044618658097711785492504343953926634992332820282019728792003956564819968'), BigInt(0)], 32 | [BigInt('57896044618658097711785492504343953926634992332820282019728792003956564819969'), BigInt(2)] 33 | ])('should double the uint256', async (input1: bigint, expected: bigint) => { 34 | const u32Input1 = { u32Inputs: bigIntToU32Array(input1), individualInputSize: FIELD_SIZE }; 35 | 36 | const evalString = u256EntryEvaluationString('u256_double', [u32Input1]); 37 | const result = await ((await browser.pages())[0]).evaluate(evalString); 38 | const arr = Object.values(result as object); 39 | const uint32ArrayResult = new Uint32Array(arr); 40 | const bigIntResult = u32ArrayToBigInts(uint32ArrayResult)[0]; 41 | 42 | expect(bigIntResult.toString()).toEqual(expected.toString()); 43 | }); 44 | }); -------------------------------------------------------------------------------- /src/gpu/entries/u256/u256Entry.ts: -------------------------------------------------------------------------------- 1 | import { U256_SIZE } from "../../U32Sizes"; 2 | import { gpuU32Inputs } from "../../utils"; 3 | import { U256WGSL } from "../../wgsl/U256"; 4 | import { batchedEntry } from "../entryCreator"; 5 | 6 | export const u256_entry = async ( 7 | wgslFunction: string, 8 | inputs: gpuU32Inputs[], 9 | batchSize?: number 10 | ) => { 11 | let inputBindings = ''; 12 | let args = ''; 13 | for (let i = 0; i < inputs.length; i++) { 14 | inputBindings += `@group(0) @binding(${i})\n 15 | var input${i}: array;\n`; 16 | args += `input${i}[global_id.x],` 17 | } 18 | // drop end comma from args 19 | args.slice(0, -1); 20 | const outputBindings = `@group(0) @binding(${inputs.length})\n 21 | var output: array;\n`; 22 | 23 | const shaderEntry = ` 24 | ${inputBindings} 25 | ${outputBindings} 26 | 27 | @compute @workgroup_size(64) 28 | fn main( 29 | @builtin(global_invocation_id) 30 | global_id : vec3 31 | ) { 32 | var result = ${wgslFunction}(${args}); 33 | output[global_id.x] = result; 34 | } 35 | `; 36 | 37 | const shaderModules = [U256WGSL, shaderEntry]; 38 | 39 | return await batchedEntry(inputs, shaderModules.join(''), U256_SIZE, batchSize); 40 | } 41 | 42 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 43 | (window as any).u256_entry = u256_entry; -------------------------------------------------------------------------------- /src/gpu/entries/u256/u256GT.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntToU32Array, gpuU32Inputs, gpuU32PuppeteerString, u32ArrayToBigInts } from '../../utils'; 4 | import { FIELD_SIZE } from '../../U32Sizes'; 5 | 6 | describe('u256GT', () => { 7 | let browser: Browser; 8 | beforeAll(async () => { 9 | browser = await puppeteer.launch({ 10 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 11 | devtools: true, 12 | headless: false, 13 | args: ['#enable-webgpu-developer-features'] 14 | }); 15 | 16 | const page = (await browser.pages())[0]; 17 | await page.goto("http://localhost:4000"); 18 | }); 19 | 20 | afterAll(async () => { 21 | await browser.close(); 22 | }); 23 | 24 | // for ease of testing, using 0s and 1s instead of true and false 25 | it.each([ 26 | [BigInt(6), BigInt(4), BigInt(1)], 27 | [BigInt(4), BigInt(6), BigInt(0)], 28 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), BigInt('1'), BigInt(1)], 29 | [BigInt(0), BigInt(0), BigInt(0)], 30 | [BigInt(9), BigInt(8), BigInt(1)], 31 | [BigInt(4294967299), BigInt(4294967297), BigInt(1)], 32 | [BigInt(3458380512), BigInt(3458380512), BigInt(0)], 33 | ])('should compare greater than uint256 numbers', async (input1: bigint, input2: bigint, expected: bigint) => { 34 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input1), individualInputSize: FIELD_SIZE }; 35 | const u32Input2: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input2), individualInputSize: FIELD_SIZE }; 36 | const result = await ((await browser.pages())[0]).evaluate(`(u256_gt)(${gpuU32PuppeteerString(u32Input1)}, ${gpuU32PuppeteerString(u32Input2)})`); 37 | const arr = Object.values(result as object); 38 | const uint32ArrayResult = new Uint32Array(arr); 39 | const bigIntResult = u32ArrayToBigInts(uint32ArrayResult)[0]; 40 | 41 | expect(bigIntResult.toString()).toEqual(expected.toString()); 42 | }); 43 | }); -------------------------------------------------------------------------------- /src/gpu/entries/u256/u256GTEntry.ts: -------------------------------------------------------------------------------- 1 | import { U256_SIZE } from "../../U32Sizes"; 2 | import { gpuU32Inputs } from "../../utils"; 3 | import { U256WGSL } from "../../wgsl/U256"; 4 | import { batchedEntry } from "../entryCreator" 5 | 6 | export const u256_gt = async ( 7 | input1: gpuU32Inputs, 8 | input2: gpuU32Inputs, 9 | batchSize?: number 10 | ) => { 11 | const shaderEntry = ` 12 | @group(0) @binding(0) 13 | var input1: array; 14 | @group(0) @binding(1) 15 | var input2: array; 16 | @group(0) @binding(2) 17 | var output: array; 18 | 19 | @compute @workgroup_size(64) 20 | fn main( 21 | @builtin(global_invocation_id) 22 | global_id : vec3 23 | ) { 24 | var gt_result = gt(input1[global_id.x], input2[global_id.x]); 25 | var result_as_uint_256: u256 = u256(array(0, 0, 0, 0, 0, 0, 0, 0)); 26 | if (gt_result) { 27 | result_as_uint_256.components[7u] = 1u; 28 | } 29 | output[global_id.x] = result_as_uint_256; 30 | } 31 | `; 32 | 33 | const shaderModules = [U256WGSL, shaderEntry]; 34 | 35 | return await batchedEntry([input1, input2], shaderModules.join(''), U256_SIZE, batchSize); 36 | } 37 | 38 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 39 | (window as any).u256_gt = u256_gt; -------------------------------------------------------------------------------- /src/gpu/entries/u256/u256RightShift.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from "puppeteer"; 2 | import { Browser } from "puppeteer"; 3 | import { bigIntToU32Array, gpuU32Inputs, gpuU32PuppeteerString, u32ArrayToBigInts } from "../../utils"; 4 | import { FIELD_SIZE } from "../../U32Sizes"; 5 | 6 | describe("u256RightShift", () => { 7 | let browser: Browser; 8 | beforeAll(async () => { 9 | browser = await puppeteer.launch({ 10 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 11 | devtools: true, 12 | headless: false, 13 | args: ['#enable-webgpu-developer-features'] 14 | }); 15 | 16 | const page = (await browser.pages())[0]; 17 | await page.goto("http://localhost:4000"); 18 | }); 19 | 20 | afterAll(async () => { 21 | await browser.close(); 22 | }); 23 | // 11111111111111111111111111111111 24 | // 00000000000000000000000000000000 25 | it.each([ 26 | [BigInt(8), 1, BigInt(4)], 27 | [BigInt(4), 1, BigInt(2)], 28 | [BigInt(2), 1, BigInt(1)], 29 | [BigInt(1), 1, BigInt(0)], 30 | [BigInt('71777214294589695'), 1, BigInt('35888607147294847')], 31 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 1, BigInt('57896044618658097711785492504343953926634992332820282019728792003956564819967')], 32 | [BigInt('26959946679704843269824881528045047742743426419429633011166591582216'), 5, BigInt('842498333740776352182027547751407741960732075607176031598955986944')], 33 | // (1, 0, 0, 0, 0, 0, 0, 0) 34 | [BigInt('26959946667150639794667015087019630673637144422540572481103610249216'), 1, BigInt('13479973333575319897333507543509815336818572211270286240551805124608')], 35 | // (0, 1, 0, 0, 0, 0, 0, 0) 36 | [BigInt('6277101735386680763835789423207666416102355444464034512896'), 1, BigInt('3138550867693340381917894711603833208051177722232017256448')], 37 | // (0, 0, 1, 0, 0, 0, 0, 0) 38 | [BigInt('1461501637330902918203684832716283019655932542976'), 1, BigInt('730750818665451459101842416358141509827966271488')], 39 | // (0, 0, 0, 1, 0, 0, 0, 0) 40 | [BigInt('340282366920938463463374607431768211456'), 1, BigInt('170141183460469231731687303715884105728')], 41 | // (0, 0, 0, 0, 1, 0, 0, 0) 42 | [BigInt('79228162514264337593543950336'), 1, BigInt('39614081257132168796771975168')], 43 | // (0, 0, 0, 0, 0, 1, 0, 0) 44 | [BigInt('18446744073709551616'), 1, BigInt('9223372036854775808')], 45 | // (0, 0, 0, 0, 0, 0, 1, 0) 46 | [BigInt('4294967296'), 1, BigInt('2147483648')], 47 | // Big shift tests 48 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 256, BigInt(0)], 49 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 224, BigInt(4294967295)], 50 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 192, BigInt('18446744073709551615')], 51 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 160, BigInt('79228162514264337593543950335')], 52 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 128, BigInt('340282366920938463463374607431768211455')], 53 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 96, BigInt('1461501637330902918203684832716283019655932542975')], 54 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 64, BigInt('6277101735386680763835789423207666416102355444464034512895')], 55 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 32, BigInt('26959946667150639794667015087019630673637144422540572481103610249215')], 56 | // 5 leftover to shift 57 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 251, BigInt(31)], 58 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 219, BigInt('137438953471')], 59 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 187, BigInt('590295810358705651711')], 60 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 155, BigInt('2535301200456458802993406410751')], 61 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 123, BigInt('10889035741470030830827987437816582766591')], 62 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 91, BigInt('46768052394588893382517914646921056628989841375231')], 63 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 59, BigInt('200867255532373784442745261542645325315275374222849104412671')], 64 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 27, BigInt('862718293348820473429344482784628181556388621521298319395315527974911')], 65 | // 31 leftover to shift 66 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 225, BigInt('2147483647')], 67 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), 193, BigInt('9223372036854775807')], 68 | ])('should right shift the uint256 by shift', async (input1: bigint, shift: number, expected: bigint) => { 69 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input1), individualInputSize: FIELD_SIZE }; 70 | 71 | const result = await ((await browser.pages())[0]).evaluate(`(u256_right_shift)(${gpuU32PuppeteerString(u32Input1)}, ${shift})`); 72 | const arr = Object.values(result as object); 73 | const uint32ArrayResult = new Uint32Array(arr); 74 | const bigIntResult = u32ArrayToBigInts(uint32ArrayResult)[0]; 75 | 76 | expect(bigIntResult.toString()).toEqual(expected.toString()); 77 | }); 78 | }); -------------------------------------------------------------------------------- /src/gpu/entries/u256/u256RightShiftEntry.ts: -------------------------------------------------------------------------------- 1 | import { U256_SIZE } from "../../U32Sizes"; 2 | import { gpuU32Inputs } from "../../utils"; 3 | import { U256WGSL } from "../../wgsl/U256"; 4 | import { batchedEntry } from "../entryCreator"; 5 | 6 | export const u256_right_shift = async ( 7 | input1: gpuU32Inputs, 8 | input2: number, 9 | batchSize?: number 10 | ) => { 11 | const shaderEntry = ` 12 | @group(0) @binding(0) 13 | var input1: array; 14 | @group(0) @binding(1) 15 | var output: array; 16 | 17 | @compute @workgroup_size(64) 18 | fn main( 19 | @builtin(global_invocation_id) 20 | global_id : vec3 21 | ) { 22 | var result = u256_right_shift(input1[global_id.x], ${input2}u); 23 | output[global_id.x] = result; 24 | } 25 | `; 26 | 27 | const shaderModules = [U256WGSL, shaderEntry]; 28 | 29 | return await batchedEntry([input1], shaderModules.join(''), U256_SIZE, batchSize); 30 | }; 31 | 32 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 33 | (window as any).u256_right_shift = u256_right_shift; -------------------------------------------------------------------------------- /src/gpu/entries/u256/u256Sub.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntToU32Array, gpuU32Inputs, u32ArrayToBigInts } from '../../utils'; 4 | import { u256EntryEvaluationString } from './evalString'; 5 | import { FIELD_SIZE } from '../../U32Sizes'; 6 | 7 | describe('u256Sub', () => { 8 | let browser: Browser; 9 | beforeAll(async () => { 10 | browser = await puppeteer.launch({ 11 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 12 | devtools: true, 13 | headless: false, 14 | args: ['#enable-webgpu-developer-features'] 15 | }); 16 | 17 | const page = (await browser.pages())[0]; 18 | await page.goto("http://localhost:4000"); 19 | }); 20 | 21 | afterAll(async () => { 22 | await browser.close(); 23 | }); 24 | 25 | it.each([ 26 | [BigInt(6), BigInt(4), BigInt(2)], 27 | [BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239041'), BigInt(1), BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239040')], 28 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), BigInt('1'), BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639934')], 29 | [BigInt(0), BigInt(0), BigInt(0)], 30 | [BigInt(9), BigInt(8), BigInt(1)], 31 | [BigInt(4294967299), BigInt(4294967297), BigInt(2)], 32 | [BigInt(3458380512), BigInt(3458380512), BigInt(0)], 33 | [BigInt('28000000000000000000000000000000000000'), BigInt('14000000000000000000000000000000000000'), BigInt('14000000000000000000000000000000000000')], 34 | [BigInt('18446744073709551616'), BigInt(1), BigInt('18446744073709551615')] 35 | ])('should subtract uint256 numbers', async (input1: bigint, input2: bigint, expected: bigint) => { 36 | // need to pass an untyped array here 37 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input1), individualInputSize: FIELD_SIZE }; 38 | const u32Input2: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input2), individualInputSize: FIELD_SIZE }; 39 | const evalString = u256EntryEvaluationString('u256_sub', [u32Input1, u32Input2]); 40 | const result = await ((await browser.pages())[0]).evaluate(evalString); 41 | const arr = Object.values(result as object); 42 | const uint32ArrayResult = new Uint32Array(arr); 43 | const bigIntResult = u32ArrayToBigInts(uint32ArrayResult)[0]; 44 | 45 | expect(bigIntResult.toString()).toEqual(expected.toString()); 46 | }); 47 | }); -------------------------------------------------------------------------------- /src/gpu/entries/u256/u256SubW.test.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import { Browser } from 'puppeteer'; 3 | import { bigIntToU32Array, gpuU32Inputs, u32ArrayToBigInts } from '../../utils'; 4 | import { u256EntryEvaluationString } from './evalString'; 5 | import { FIELD_SIZE } from '../../U32Sizes'; 6 | 7 | describe('u256Sub', () => { 8 | let browser: Browser; 9 | beforeAll(async () => { 10 | browser = await puppeteer.launch({ 11 | executablePath: '/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta', 12 | devtools: true, 13 | headless: false, 14 | args: ['#enable-webgpu-developer-features'] 15 | }); 16 | 17 | const page = (await browser.pages())[0]; 18 | await page.goto("http://localhost:4000"); 19 | }); 20 | 21 | afterAll(async () => { 22 | await browser.close(); 23 | }); 24 | 25 | it.each([ 26 | [BigInt(6), BigInt(4), BigInt(2)], 27 | [BigInt(1), BigInt(2), BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935')], 28 | [BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239041'), BigInt(1), BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239040')], 29 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), BigInt('1'), BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639934')], 30 | [BigInt(0), BigInt(0), BigInt(0)], 31 | [BigInt(0), BigInt(1), BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935')], 32 | [BigInt(9), BigInt(8), BigInt(1)], 33 | [BigInt(4294967299), BigInt(4294967297), BigInt(2)], 34 | [BigInt(3458380512), BigInt(3458380512), BigInt(0)], 35 | [BigInt('14000000000000000000000000000000000000'), BigInt('28000000000000000000000000000000000000'), BigInt('115792089237316195423570985008687907853255984665640564039457584007913129639936')], 36 | ])('should subtract uint256 numbers', async (input1: bigint, input2: bigint, expected: bigint) => { 37 | const u32Input1: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input1), individualInputSize: FIELD_SIZE }; 38 | const u32Input2: gpuU32Inputs = { u32Inputs: bigIntToU32Array(input2), individualInputSize: FIELD_SIZE }; 39 | const evalString = u256EntryEvaluationString('u256_subw', [u32Input1, u32Input2]); 40 | const result = await ((await browser.pages())[0]).evaluate(evalString); 41 | const arr = Object.values(result as object); 42 | const uint32ArrayResult = new Uint32Array(arr); 43 | const bigIntResult = u32ArrayToBigInts(uint32ArrayResult)[0]; 44 | 45 | expect(bigIntResult.toString()).toEqual(expected.toString()); 46 | }); 47 | }); -------------------------------------------------------------------------------- /src/gpu/prune.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prunes unused functions from the given shader code string. 3 | * Keeps only the functions listed in the functionNames array and the functions 4 | * that are recursively called by them. 5 | * 6 | * @param {string} shaderCode - The original shader code as a string. 7 | * @param {string[]} functionNames - An array of function names that should be retained in the shader code along with functions that are called by them, directly or recursively. 8 | * 9 | * @returns {string} - The pruned shader code as a string. 10 | */ 11 | export function prune(shaderCode: string, functionNames: string[]): string { 12 | // Remove all single line and multi-line comments 13 | shaderCode = shaderCode.replace(/\/\/[^\n]*|\/\*[\s\S]*?\*\//g, ''); 14 | 15 | const functions = new Map(); 16 | const functionPattern = /fn\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\([^]*?->[^]*?\{/g; 17 | const globalStructsAndConstsPattern = /(struct\s+[a-zA-Z_][a-zA-Z_0-9]*\s*\{[^]*?\})|(\bconst\b[^;]*;)|(\balias\b\s+[a-zA-Z_][a-zA-Z_0-9]*\s*=\s*[a-zA-Z_][a-zA-Z_0-9]*\s*;)/g; 18 | 19 | let match; 20 | 21 | // Extract all global structs, constants, and aliases 22 | let globalStructsAndConsts = ''; 23 | while ((match = globalStructsAndConstsPattern.exec(shaderCode)) !== null) { 24 | globalStructsAndConsts += match[0] + '\n\n'; 25 | } 26 | 27 | // Extract all functions 28 | while ((match = functionPattern.exec(shaderCode)) !== null) { 29 | let braceCount = 1; 30 | let endIndex = match.index + match[0].length; 31 | while (braceCount > 0 && endIndex < shaderCode.length) { 32 | if (shaderCode[endIndex] === '{') { 33 | braceCount++; 34 | } else if (shaderCode[endIndex] === '}') { 35 | braceCount--; 36 | } 37 | endIndex++; 38 | } 39 | 40 | const funcBody = shaderCode.slice(match.index, endIndex); 41 | functions.set(match[1], funcBody); 42 | } 43 | 44 | const usedFunctions = new Set(functionNames); 45 | 46 | function addCalledFunctions(functionBody: string) { 47 | for (const [name, body] of functions.entries()) { 48 | if (functionBody.includes(name)) { 49 | if (!usedFunctions.has(name)) { 50 | usedFunctions.add(name); 51 | addCalledFunctions(body); // recursive call to handle nested function calls 52 | } 53 | } 54 | } 55 | } 56 | 57 | functionNames.forEach(fnName => { 58 | const body = functions.get(fnName); 59 | if (body) { 60 | addCalledFunctions(body); 61 | } 62 | }); 63 | 64 | let prunedCode = globalStructsAndConsts; 65 | for (const usedFunction of usedFunctions.values()) { 66 | prunedCode += functions.get(usedFunction) + '\n\n'; 67 | } 68 | 69 | return prunedCode; 70 | } -------------------------------------------------------------------------------- /src/gpu/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_MODULUS as BLS12_377_MODULUS } from "../params/BLS12_377Constants"; 2 | import { FQ_FIELD_MODULUS as BN254_MODULUS } from "../params/BN254Constants"; 3 | import { bigIntToU32Array, bigIntsToU32Array, u32ArrayToBigInts } from "./utils"; 4 | 5 | const testData: [bigint, Uint32Array][] = [ 6 | [BigInt(0), new Uint32Array([0, 0, 0, 0, 0, 0, 0, 0])], 7 | [BigInt(1), new Uint32Array([0, 0, 0, 0, 0, 0, 0, 1])], 8 | [BigInt(33), new Uint32Array([0, 0, 0, 0, 0, 0, 0, 33])], 9 | [BigInt(4294967297), new Uint32Array([0, 0, 0, 0, 0, 0, 1, 1])], 10 | [BLS12_377_MODULUS, new Uint32Array([313222494, 2586617174, 1622428958, 1547153409, 1504343806, 3489660929, 168919040, 1])], 11 | [BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), new Uint32Array([4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295])], 12 | [BigInt('6924886788847882060123066508223519077232160750698452411071850219367055984476'), new Uint32Array([256858326, 3006847798, 1208683936, 2370827163, 3854692792, 1079629005, 1919445418, 2787346268])], 13 | [BigInt('60001509534603559531609739528203892656505753216962260608619555'), new Uint32Array([0, 9558, 3401397337, 1252835688, 2587670639, 1610789716, 3992821760, 136227])], 14 | [BigInt('30000754767301779765804869764101946328252876608481130304309778'), new Uint32Array([0, 4779, 1700698668, 2773901492, 1293835319, 2952878506, 1996410880, 68114])], 15 | [BigInt('5472060717959818805561601436314318772174077789324455915672259473661306552146'), new Uint32Array([0,0,0,0,0,0,0,0])], 16 | [BN254_MODULUS, new Uint32Array([811880050, 3778125865, 3092268470, 2172737629, 2541841041, 1752287885, 1008765974, 3632069959])] 17 | ]; 18 | 19 | describe('utils', () => { 20 | describe('bigIntsToU32Array', () => { 21 | it.each([ 22 | [BigInt(0), new Uint32Array([0, 0, 0, 0, 0, 0, 0, 0])], 23 | [BigInt(1), new Uint32Array([0, 0, 0, 0, 0, 0, 0, 1])], 24 | [BigInt(33), new Uint32Array([0, 0, 0, 0, 0, 0, 0, 33])], 25 | [BigInt(4294967297), new Uint32Array([0, 0, 0, 0, 0, 0, 1, 1])] 26 | ])('should convert bigints to u32 arrays', (bigIntToConvert: bigint, expectedU32Array: Uint32Array) => { 27 | const u32Array = bigIntsToU32Array([bigIntToConvert]); 28 | expect(u32Array).toEqual(expectedU32Array); 29 | }); 30 | }); 31 | 32 | describe('bigIntToU32Array', () => { 33 | it.each(testData)('should convert a bigint to u32 array', (bigIntToConvert: bigint, expectedU32Array: Uint32Array) => { 34 | const u32Array = bigIntToU32Array(bigIntToConvert); 35 | expect(u32Array).toEqual(expectedU32Array); 36 | }); 37 | }); 38 | 39 | describe('u32ArrayToBigInts', () => { 40 | it.each(testData)('should convert a bigint to u32 array', (expectedBigInt: bigint, u32Array: Uint32Array) => { 41 | const bigInt = u32ArrayToBigInts(u32Array); 42 | expect(bigInt).toEqual([expectedBigInt]); 43 | }); 44 | }); 45 | }); -------------------------------------------------------------------------------- /src/gpu/utils.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_MODULUS as BLS12_377_MODULUS } from "../params/BLS12_377Constants"; 2 | import { FQ_FIELD_MODULUS as BN254_MODULUS, FR_FIELD_MODULUS } from "../params/BN254Constants"; 3 | import { CurveType } from "./curveSpecific"; 4 | 5 | export const bigIntsToU16Array = (beBigInts: bigint[]): Uint16Array => { 6 | const intsAs16s = beBigInts.map(bigInt => bigIntToU16Array(bigInt)); 7 | const u16Array = new Uint16Array(beBigInts.length * 16); 8 | intsAs16s.forEach((intAs16, index) => {u16Array.set(intAs16, index * 16)}); 9 | return u16Array; 10 | } 11 | 12 | export const bigIntToU16Array = (beBigInt: bigint): Uint16Array => { 13 | const numBits = 256; 14 | const bitsPerElement = 16; 15 | const numElements = numBits / bitsPerElement; 16 | const u16Array = new Uint16Array(numElements); 17 | const mask = (BigInt(1) << BigInt(bitsPerElement)) - BigInt(1); // Create a mask for the lower 32 bits 18 | 19 | let tempBigInt = beBigInt; 20 | for (let i = numElements - 1; i >= 0; i--) { 21 | u16Array[i] = Number(tempBigInt & mask); // Extract the lower 32 bits 22 | tempBigInt >>= BigInt(bitsPerElement); // Right-shift the remaining bits 23 | } 24 | 25 | return u16Array; 26 | }; 27 | 28 | // assume bigints are big endian 256-bit integers 29 | export const bigIntsToU32Array = (beBigInts: bigint[]): Uint32Array => { 30 | const intsAs32s = beBigInts.map(bigInt => bigIntToU32Array(bigInt)); 31 | const u32Array = new Uint32Array(beBigInts.length * 8); 32 | intsAs32s.forEach((intAs32, index) => {u32Array.set(intAs32, index * 8)}); 33 | return u32Array; 34 | }; 35 | 36 | export const bigIntToU32Array = (beBigInt: bigint): Uint32Array => { 37 | const numBits = 256; 38 | const bitsPerElement = 32; 39 | const numElements = numBits / bitsPerElement; 40 | const u32Array = new Uint32Array(numElements); 41 | const mask = (BigInt(1) << BigInt(bitsPerElement)) - BigInt(1); // Create a mask for the lower 32 bits 42 | 43 | let tempBigInt = beBigInt; 44 | for (let i = numElements - 1; i >= 0; i--) { 45 | u32Array[i] = Number(tempBigInt & mask); // Extract the lower 32 bits 46 | tempBigInt >>= BigInt(bitsPerElement); // Right-shift the remaining bits 47 | } 48 | 49 | return u32Array; 50 | }; 51 | 52 | export const u32ArrayToBigInts = (u32Array: Uint32Array): bigint[] => { 53 | const bigInts = []; 54 | for (let i = 0; i < u32Array.length; i += 8) { 55 | let bigInt = BigInt(0); 56 | for (let j = 0; j < 8 && (i + j) < u32Array.length; j++) { 57 | bigInt = (bigInt << BigInt(32)) | BigInt(u32Array[i + j]); 58 | } 59 | bigInts.push(bigInt); 60 | } 61 | return bigInts; 62 | }; 63 | 64 | export interface gpuU32Inputs { 65 | u32Inputs: Uint32Array; 66 | individualInputSize: number; 67 | } 68 | 69 | export const gpuU32PuppeteerString = (gpuU32Input: gpuU32Inputs): string => { 70 | let puppeteerString = `{ u32Inputs: Uint32Array.from([${gpuU32Input.u32Inputs}])`; 71 | 72 | puppeteerString += ', individualInputSize: ' + gpuU32Input.individualInputSize + '}'; 73 | return puppeteerString; 74 | }; 75 | 76 | export const chunkArray = (inputsArray: gpuU32Inputs[], batchSize: number): gpuU32Inputs[][] => { 77 | let index = 0; 78 | const chunkedArray: gpuU32Inputs[][] = []; 79 | const firstInputLength = inputsArray[0].u32Inputs.length / inputsArray[0].individualInputSize; 80 | 81 | while (index < firstInputLength) { 82 | const newIndex = index + batchSize; 83 | const tempArray: gpuU32Inputs[] = []; 84 | inputsArray.forEach(bufferData => { 85 | const chunkedGpuU32Inputs = bufferData.u32Inputs.slice(index * bufferData.individualInputSize, newIndex * bufferData.individualInputSize); 86 | tempArray.push({ 87 | u32Inputs: chunkedGpuU32Inputs, 88 | individualInputSize: bufferData.individualInputSize 89 | }); 90 | }); 91 | index = newIndex; 92 | chunkedArray.push(tempArray); 93 | } 94 | 95 | return chunkedArray; 96 | }; 97 | 98 | export const chunkGPUInputs = (inputsArray: gpuU32Inputs[], batchSize: number, inputsToBatch: number[]): gpuU32Inputs[][] => { 99 | const chunkedArray: gpuU32Inputs[][] = []; 100 | const numInputs = inputsArray[0].u32Inputs.length / inputsArray[0].individualInputSize; 101 | 102 | let numBatched = 0 103 | while (numBatched < numInputs) { 104 | const chunkedInputs: gpuU32Inputs[] = []; 105 | for (let i = 0; i < inputsArray.length; i++) { 106 | const bufferData = inputsArray[i]; 107 | const shouldBatch = inputsToBatch.length === 0 || inputsToBatch.includes(i); 108 | if (shouldBatch) { 109 | const chunkedGpuU32Inputs = bufferData.u32Inputs.slice(numBatched * bufferData.individualInputSize, (numBatched + batchSize) * bufferData.individualInputSize); 110 | chunkedInputs.push({ 111 | u32Inputs: chunkedGpuU32Inputs, 112 | individualInputSize: bufferData.individualInputSize 113 | }); 114 | } else { 115 | // This isn't an input that we should batch, so we assume that it should be the same every pass 116 | chunkedInputs.push(bufferData); 117 | } 118 | } 119 | numBatched += batchSize; 120 | chunkedArray.push(chunkedInputs); 121 | } 122 | 123 | return chunkedArray; 124 | }; 125 | 126 | const pushOrCreateArray = (array: any[], index: number, value: any) => { 127 | if (!array[index]) { 128 | array[index] = []; 129 | } 130 | array[index].push(value); 131 | } 132 | 133 | export const generateRandomFields = (inputSize: number, curve: CurveType): bigint[] => { 134 | const randomBigInts = []; 135 | let fieldModulus = BigInt(0); 136 | switch (curve) { 137 | case CurveType.BN254: 138 | fieldModulus = BN254_MODULUS; 139 | break; 140 | case CurveType.BLS12_377: 141 | fieldModulus = BLS12_377_MODULUS; 142 | break; 143 | default: 144 | throw new Error('Invalid curve type'); 145 | } 146 | for (let i = 0; i < inputSize; i++) { 147 | randomBigInts.push(createRandomNumber(fieldModulus)); 148 | } 149 | 150 | return randomBigInts; 151 | }; 152 | 153 | export const generateRandomScalars = (inputSize: number, curve: CurveType): bigint[] => { 154 | const randomBigInts = []; 155 | let fieldModulus = BigInt(0); 156 | switch (curve) { 157 | case CurveType.BN254: 158 | fieldModulus = FR_FIELD_MODULUS; 159 | break; 160 | case CurveType.BLS12_377: 161 | fieldModulus = BLS12_377_MODULUS; 162 | break; 163 | default: 164 | throw new Error('Invalid curve type'); 165 | } 166 | for (let i = 0; i < inputSize; i++) { 167 | randomBigInts.push(createRandomNumber(fieldModulus)); 168 | } 169 | 170 | return randomBigInts; 171 | }; 172 | 173 | export const convertBigIntsToWasmFields = (bigInts: bigint[]): string[] => { 174 | return bigInts.map(bigInt => bigInt.toString() + 'field'); 175 | }; 176 | 177 | const createRandomNumber = (modulus: bigint) => { 178 | let bigIntString = ''; 179 | for (let i = 0; i < 8; i++) { 180 | bigIntString += Math.floor(Math.random() * (2**32 - 1)); 181 | } 182 | // const modResult = BigInt(1) % modulus; 183 | const modResult = BigInt(bigIntString) % modulus; 184 | return modResult; 185 | } 186 | 187 | export const stripFieldSuffix = (field: string): string => { 188 | return field.slice(0, field.length - 5); 189 | }; 190 | 191 | export const stripGroupSuffix = (group: string): string => { 192 | return group.slice(0, group.length - 5); 193 | }; -------------------------------------------------------------------------------- /src/gpu/wgsl/AleoPoseidon.ts: -------------------------------------------------------------------------------- 1 | export const AleoPoseidonWGSL = ` 2 | fn poseidon_round_full(inputs: array, roundNum: u32) -> array { 3 | // Update inputs. NewInputs will be mutated. 4 | var newInputs: array = inputs; 5 | 6 | // Add round constants 7 | for (var i = 0u; i < 9u; i++) { 8 | var field = newInputs[i]; 9 | 10 | var sum = field_add(field, aleoRoundConstants[roundNum][i]); 11 | newInputs[i] = sum; 12 | } 13 | 14 | for (var i = 0u; i < 9u; i++) { 15 | newInputs[i] = field_pow_by_17(newInputs[i]); 16 | } 17 | 18 | // Matrix multiplication, but single threaded lol 19 | var result: array = newInputs; 20 | for (var i = 0u; i < 9u; i++) { 21 | var accum = U256_ZERO; 22 | var aleoMdArray = aleoMds[i]; 23 | for (var j = 0u; j < 9u; j++) { 24 | var mult = field_multiply(newInputs[j], aleoMdArray[j]); 25 | accum = field_add(accum, mult); 26 | } 27 | result[i] = accum; 28 | } 29 | 30 | return result; 31 | }; 32 | 33 | fn poseidon_round_partial(inputs: array, roundNum: u32) -> array { 34 | // Update inputs. NewInputs will be mutated. 35 | var newInputs: array = inputs; 36 | 37 | // Add round constants 38 | for (var i = 0u; i < 9u; i++) { 39 | var field = newInputs[i]; 40 | 41 | var sum = field_add(field, aleoRoundConstants[roundNum][i]); 42 | newInputs[i] = sum; 43 | } 44 | 45 | var pow = field_pow_by_17(newInputs[0]); 46 | newInputs[0] = pow; 47 | 48 | // Matrix multiplication, but single threaded lol 49 | var result: array = newInputs; 50 | for (var i = 0u; i < 9u; i++) { 51 | var accum = u256(array(0, 0, 0, 0, 0, 0, 0, 0)); 52 | var aleoMdArray = aleoMds[i]; 53 | for (var j = 0u; j < 9u; j++) { 54 | var mult = field_multiply(newInputs[j], aleoMdArray[j]); 55 | accum = field_add(accum, mult); 56 | } 57 | result[i] = accum; 58 | } 59 | 60 | return result; 61 | } 62 | 63 | fn poseidon_hash(inputs: array) -> array { 64 | var values = inputs; 65 | var roundNum = 0u; 66 | for (var i = 0u; i < 4u; i++) { 67 | values = poseidon_round_full(values, roundNum); 68 | roundNum += 1u; 69 | } 70 | 71 | for (var i = 0u; i < 31u; i++) { 72 | values = poseidon_round_partial(values, roundNum); 73 | roundNum += 1u; 74 | } 75 | 76 | for (var i = 0u; i < 4u; i++) { 77 | values = poseidon_round_full(values, roundNum); 78 | roundNum += 1u; 79 | } 80 | 81 | return values; 82 | } 83 | 84 | fn aleo_poseidon(recordViewKey: Field) -> Field { 85 | var firstHashOutput = POSEIDON_FIRST_HASH_OUTPUT; 86 | var secondElementPlus = field_add(firstHashOutput[1], ENCRYPTION_DOMAIN); 87 | var thirdElementPlus = field_add(firstHashOutput[2], recordViewKey); 88 | firstHashOutput[1] = secondElementPlus; 89 | firstHashOutput[2] = thirdElementPlus; 90 | 91 | var secondHashOutput = poseidon_hash(firstHashOutput); 92 | return secondHashOutput[1]; 93 | } 94 | `; 95 | 96 | export const PoseidonFirstHashOutputWGSL = ` 97 | fn poseidon_first_hash_output(recordViewKey: Field) -> array { 98 | var firstHashOutput = POSEIDON_FIRST_HASH_OUTPUT; 99 | var secondElementPlus = field_add(firstHashOutput[1], ENCRYPTION_DOMAIN); 100 | var thirdElementPlus = field_add(firstHashOutput[2], recordViewKey); 101 | firstHashOutput[1] = secondElementPlus; 102 | firstHashOutput[2] = thirdElementPlus; 103 | 104 | return firstHashOutput; 105 | } 106 | `; 107 | 108 | export const PoseidonRoundPartialWGSL = ` 109 | fn poseidon_round_partial(inputs: array, roundNum: u32) -> array { 110 | // Update inputs. NewInputs will be mutated. 111 | var newInputs: array = inputs; 112 | 113 | // Add round constants 114 | for (var i = 0u; i < 9u; i++) { 115 | var field = newInputs[i]; 116 | 117 | var sum = field_add(field, aleoRoundConstants[roundNum][i]); 118 | newInputs[i] = sum; 119 | } 120 | 121 | var pow = field_pow_by_17(newInputs[0]); 122 | newInputs[0] = pow; 123 | 124 | // Matrix multiplication, but single threaded lol 125 | var result: array = newInputs; 126 | for (var i = 0u; i < 9u; i++) { 127 | var accum = u256(array(0, 0, 0, 0, 0, 0, 0, 0)); 128 | var aleoMdArray = aleoMds[i]; 129 | for (var j = 0u; j < 9u; j++) { 130 | var mult = field_multiply(newInputs[j], aleoMdArray[j]); 131 | accum = field_add(accum, mult); 132 | } 133 | result[i] = accum; 134 | } 135 | 136 | return result; 137 | } 138 | `; 139 | 140 | export const PoseidonRoundFullWGSL = ` 141 | fn poseidon_round_full(inputs: array, roundNum: u32) -> array { 142 | // Update inputs. NewInputs will be mutated. 143 | var newInputs: array = inputs; 144 | 145 | // Add round constants 146 | for (var i = 0u; i < 9u; i++) { 147 | var field = newInputs[i]; 148 | 149 | var sum = field_add(field, aleoRoundConstants[roundNum][i]); 150 | newInputs[i] = sum; 151 | } 152 | 153 | for (var i = 0u; i < 9u; i++) { 154 | newInputs[i] = field_pow_by_17(newInputs[i]); 155 | } 156 | 157 | // Matrix multiplication, but single threaded lol 158 | var result: array = newInputs; 159 | for (var i = 0u; i < 9u; i++) { 160 | var accum = U256_ZERO; 161 | var aleoMdArray = aleoMds[i]; 162 | for (var j = 0u; j < 9u; j++) { 163 | var mult = field_multiply(newInputs[j], aleoMdArray[j]); 164 | accum = field_add(accum, mult); 165 | } 166 | result[i] = accum; 167 | } 168 | 169 | return result; 170 | }; 171 | `; -------------------------------------------------------------------------------- /src/gpu/wgsl/AleoPoseidonConstants.ts: -------------------------------------------------------------------------------- 1 | export const AleoPoseidonConstantsWGSL = ` 2 | const POSEIDON_FIRST_HASH_OUTPUT = array( 3 | u256( 4 | array(193209980, 3492865298, 3794906262, 3220028184, 934940190, 2559616501, 2038945397, 2653696658) 5 | ), 6 | u256( 7 | array(148167849, 2122145193, 2498874779, 406659822, 3053784108, 1832238105, 2678704334, 25536486) 8 | ), 9 | u256( 10 | array(215560196, 1024986314, 1974009106, 3702936298, 3896313196, 299171506, 1476322820, 414123341) 11 | ), 12 | u256( 13 | array(239356315, 1172390106, 4155628602, 391046907, 1827095451, 1576118224, 405383935, 1822799649) 14 | ), 15 | u256( 16 | array(234957820, 3140959200, 1906433220, 2563314264, 3102224767, 589752971, 2623135042, 1889199495) 17 | ), 18 | u256( 19 | array(56924762, 3140587176, 1888177369, 172544174, 3277535103, 398197741, 3919984972, 1618780952) 20 | ), 21 | u256( 22 | array(201886118, 810112403, 2128255862, 2065752428, 450795211, 2120249687, 3290439474, 2336375883) 23 | ), 24 | u256( 25 | array(103347384, 818651613, 2835399007, 3152098707, 2057534933, 2193345497, 1136123410, 2992822358) 26 | ), 27 | u256( 28 | array(74608204, 444905711, 890933544, 3541770455, 3109323710, 3839763188, 3506673759, 1917106751) 29 | ) 30 | ); 31 | 32 | const ENCRYPTION_DOMAIN = u256( 33 | array(0, 0, 812543849, 1953528178, 1668171107, 1769108581, 1835891027, 1868917825) 34 | ); 35 | 36 | const POSEIDON_DOMAIN = u256( 37 | array(0, 0, 0, 0, 56, 1852793961, 1702063952, 1868917825) 38 | ); 39 | `; -------------------------------------------------------------------------------- /src/gpu/wgsl/BLS12-377CurveBaseWGSL.ts: -------------------------------------------------------------------------------- 1 | export const BLS12_377CurveBaseWGSL = ` 2 | const ZERO_POINT = Point (U256_ZERO, U256_ONE, U256_ZERO, U256_ONE); 3 | const ZERO_AFFINE = AffinePoint (U256_ZERO, U256_ONE); 4 | 5 | fn mul_by_a(f: Field) -> Field { 6 | // mul by a is just negation of f 7 | return u256_sub(FIELD_ORDER, f); 8 | } 9 | 10 | // follows aleo's projective addition algorithm 11 | // See "Twisted Edwards Curves Revisited" 12 | // Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, and Ed Dawson 13 | // 3.1 Unified Addition in E^e 14 | fn add_points(p1: Point, p2: Point) -> Point { 15 | var a = field_multiply(p1.x, p2.x); 16 | var b = field_multiply(p1.y, p2.y); 17 | var c = field_multiply(EDWARDS_D, field_multiply(p1.t, p2.t)); 18 | var d = field_multiply(p1.z, p2.z); 19 | var p1_added = field_add(p1.x, p1.y); 20 | var p2_added = field_add(p2.x, p2.y); 21 | var e = field_multiply(field_add(p1.x, p1.y), field_add(p2.x, p2.y)); 22 | e = field_sub(e, a); 23 | e = field_sub(e, b); 24 | var f = field_sub(d, c); 25 | var g = field_add(d, c); 26 | var a_neg = mul_by_a(a); 27 | var h = field_sub(b, a_neg); 28 | var added_x = field_multiply(e, f); 29 | var added_y = field_multiply(g, h); 30 | var added_t = field_multiply(e, h); 31 | var added_z = field_multiply(f, g); 32 | return Point(added_x, added_y, added_t, added_z); 33 | } 34 | 35 | // follows https://www.hyperelliptic.org/EFD/g1p/data/twisted/extended/doubling/dbl-2008-hwcd 36 | fn double_point(p: Point) -> Point { 37 | var a = field_multiply(p.x, p.x); 38 | var b = field_multiply(p.y, p.y); 39 | var c = field_double(field_multiply(p.z, p.z)); 40 | var d = mul_by_a(a); 41 | var e = field_add(p.x, p.y); 42 | e = field_multiply(e, e); 43 | e = field_sub(e, a); 44 | e = field_sub(e, b); 45 | var g = field_add(d, b); 46 | var f = field_sub(g, c); 47 | var h = field_sub(d, b); 48 | var doubled_x = field_multiply(e, f); 49 | var doubled_y = field_multiply(g, h); 50 | var doubled_t = field_multiply(e, h); 51 | var doubled_z = field_multiply(f, g); 52 | return Point(doubled_x, doubled_y, doubled_t, doubled_z); 53 | } 54 | 55 | fn normalize_x(x: Field, z: Field) -> Field { 56 | var z_inverse = field_inverse(z); 57 | return field_multiply(x, z_inverse); 58 | } 59 | `; -------------------------------------------------------------------------------- /src/gpu/wgsl/BLS12-377Params.ts: -------------------------------------------------------------------------------- 1 | export const BLS12_377ParamsWGSL = 2 | ` 3 | // 8444461749428370424248824938781546531375899335154063827935233455917409239041 4 | const FIELD_ORDER: Field = Field( 5 | array(313222494, 2586617174, 1622428958, 1547153409, 1504343806, 3489660929, 168919040, 1) 6 | ); 7 | 8 | // 8444461749428370424248824938781546531375899335154063827935233455917409239042 9 | const FIELD_ORDER_PLUS_ONE: Field = Field( 10 | array(313222494, 2586617174, 1622428958, 1547153409, 1504343806, 3489660929, 168919040, 2) 11 | ); 12 | 13 | // 8444461749428370424248824938781546531375899335154063827935233455917409239040 14 | const FIELD_ORDER_MINUS_ONE: Field = Field( 15 | array(313222494, 2586617174, 1622428958, 1547153409, 1504343806, 3489660929, 168919040, 0) 16 | ); 17 | 18 | // S-Tonelli computes different values given a big int n and a prime p. Because our prime is the 19 | // BLS12-377 field modulus, we have precomputed some of the values in advance to optimize for the GPU. 20 | 21 | // 6924886788847882060123066508223519077232160750698452411071850219367055984476 22 | const c_initial: Field = Field ( 23 | array(256858326, 3006847798, 1208683936, 2370827163, 3854692792, 1079629005, 1919445418, 2787346268) 24 | ); 25 | 26 | const s: Field = Field ( 27 | array(0, 0, 0, 0, 0, 0, 0, 47) 28 | ); 29 | 30 | // 60001509534603559531609739528203892656505753216962260608619555 31 | const q: Field = Field ( 32 | array(0, 9558, 3401397337, 1252835688, 2587670639, 1610789716, 3992821760, 136227) 33 | ); 34 | 35 | // 30000754767301779765804869764101946328252876608481130304309778 36 | const r_initial_exponent: Field = Field ( 37 | array(0, 4779, 1700698668, 2773901492, 1293835319, 2952878506, 1996410880, 68114) 38 | ); 39 | 40 | // 3021 41 | const EDWARDS_D: Field = Field ( 42 | array(0, 0, 0, 0, 0, 0, 0, 3021) 43 | ); 44 | 45 | const EDWARDS_D_PLUS_ONE: Field = Field( 46 | array(0, 0, 0, 0, 0, 0, 0, 3022) 47 | ); 48 | 49 | // assumes that num is indeed a square root residue. 50 | // follows the Shanks Tonelli algorithm. View shankstonelli.ts for the non-shortened version. 51 | fn field_sqrt(num: Field) -> Field { 52 | var c: Field = c_initial; 53 | var r: Field = gen_field_pow(num, r_initial_exponent, FIELD_ORDER); 54 | var t: Field = gen_field_pow(num, q, FIELD_ORDER); 55 | var m: Field = s; 56 | 57 | while (!equal(t, U256_ONE)) { 58 | var tt: Field = t; 59 | var i: Field = U256_ZERO; 60 | while (!equal(tt, U256_ONE)) { 61 | tt = field_multiply(tt, tt); 62 | i = u256_add(i, U256_ONE); 63 | if (equal(i, m)) { 64 | return U256_ZERO; 65 | } 66 | } 67 | 68 | var b_exp_exp: Field = u256_sub(m, u256_add(i, U256_ONE)); 69 | var b_exp: Field = gen_field_pow(U256_TWO, b_exp_exp, FIELD_ORDER_MINUS_ONE); 70 | var b: Field = gen_field_pow(c, b_exp, FIELD_ORDER); 71 | var b2: Field = field_multiply(b, b); 72 | r = field_multiply(r, b); 73 | t = field_multiply(t, b2); 74 | c = b2; 75 | m = i; 76 | } 77 | 78 | return r; 79 | } 80 | `; -------------------------------------------------------------------------------- /src/gpu/wgsl/BN254CurveBaseWGSL.ts: -------------------------------------------------------------------------------- 1 | export const BN254CurveBaseWGSL = ` 2 | const ZERO_POINT = Point (U256_ZERO, U256_ONE, U256_ZERO, U256_ZERO); 3 | const ZERO_AFFINE = AffinePoint (U256_ZERO, U256_ONE); 4 | 5 | // follows http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-2007-bl 6 | // fn add_points(p1: Point, p2: Point) -> Point { 7 | // var z1z1 = field_multiply(p1.z, p1.z); 8 | // var z2z2 = field_multiply(p2.z, p2.z); 9 | // var u1 = field_multiply(p1.x, z2z2); 10 | // var u2 = field_multiply(p2.x, z1z1); 11 | // var s1 = field_multiply(p1.y, field_multiply(p2.z, z2z2)); 12 | // var s2 = field_multiply(p2.y, field_multiply(p1.z, z1z1)); 13 | // var h = field_sub(u2, u1); 14 | // var sqrt_i = field_double(h); 15 | // var i = field_multiply(sqrt_i, sqrt_i); 16 | // var j = field_multiply(h, i); 17 | // var r = field_double(field_sub(s2, s1)); 18 | // var v = field_multiply(u1, i); 19 | // var r_square = field_multiply(r, r); 20 | // var intermediate_x = field_sub(r_square, j); 21 | // var x_result = field_sub(intermediate_x, field_double(v)); 22 | // var intermediate_y = field_multiply(r, field_sub(v, x_result)); 23 | // var y_result = field_sub(intermediate_y, field_double(field_multiply(s1, j))); 24 | // var t_result = field_multiply(x_result, y_result); 25 | // var zs_added = field_add(p1.z, p2.z); 26 | // var intermediate_z = field_sub(field_multiply(zs_added, zs_added), z1z1); 27 | // var z_result = field_multiply(field_sub(intermediate_z, z2z2), h); 28 | // return Point(x_result, y_result, t_result, z_result); 29 | // } 30 | 31 | fn add_points(p1: Point, p2: Point) -> Point { 32 | if (equal(p1.x, U256_ZERO) && equal(p1.y, U256_ONE) && equal(p1.z, U256_ZERO)) { 33 | return p2; 34 | } 35 | if (equal(p2.x, U256_ZERO) && equal(p2.y, U256_ONE) && equal(p2.z, U256_ZERO)) { 36 | return p1; 37 | } 38 | var z1z1 = field_multiply(p1.z, p1.z); 39 | var z2z2 = field_multiply(p2.z, p2.z); 40 | var s2 = field_multiply(z1z1, p1.z); 41 | var u2 = field_multiply(z1z1, p2.x); 42 | s2 = field_multiply(s2, p2.y); 43 | var u1 = field_multiply(z2z2, p1.x); 44 | var s1 = field_multiply(z2z2, p2.z); 45 | s1 = field_multiply(s1, p1.y); 46 | var f = field_double(field_sub(s2, s1)); 47 | if (equal(f, U256_ZERO)) { 48 | return double_point(p1); 49 | } 50 | var h = field_sub(u2, u1); 51 | var i = field_double(h); 52 | i = field_multiply(i, i); 53 | var j = field_multiply(h, i); 54 | u1 = field_multiply(u1, i); 55 | u2 = field_double(u1); 56 | u2 = field_add(u2, j); 57 | var x_result = field_multiply(f, f); 58 | x_result = field_sub(x_result, u2); 59 | j = field_multiply(j, s1); 60 | j = field_double(j); 61 | var y_result = field_sub(u1, x_result); 62 | y_result = field_multiply(f, y_result); 63 | y_result = field_sub(y_result, j); 64 | var z_result = field_add(p1.z, p2.z); 65 | z1z1 = field_add(z1z1, z2z2); 66 | z_result = field_multiply(z_result, z_result); 67 | z_result = field_sub(z_result, z1z1); 68 | z_result = field_multiply(z_result, h); 69 | var t_result = field_multiply(x_result, y_result); 70 | return Point(x_result, y_result, t_result, z_result); 71 | } 72 | 73 | // // follows http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl 74 | // fn double_point(p: Point) -> Point { 75 | // var xx = field_multiply(p.x, p.x); 76 | // var yy = field_multiply(p.y, p.y); 77 | // var yyyy = field_multiply(yy, yy); 78 | // var zz = field_multiply(p.z, p.z); 79 | // var x1_add_yy = field_add(p.x, yy); 80 | // var s_intermediate = field_sub(field_multiply(x1_add_yy, x1_add_yy), xx); 81 | // var s = field_double(field_sub(s_intermediate, yyyy)); 82 | // var m = field_multiply(U256_THREE, xx); 83 | // var x_result = field_sub(field_multiply(m, m), field_double(s)); 84 | // var intermediate_y = field_multiply(m, field_sub(s, x_result)); 85 | // var y_result = field_sub(intermediate_y, field_multiply(U256_EIGHT, yyyy)); 86 | // var t_result = field_multiply(x_result, y_result); 87 | // var y_add_z = field_add(p.y, p.z); 88 | // var y_add_z_squared = field_multiply(y_add_z, y_add_z); 89 | // var z_result = field_sub(field_sub(y_add_z_squared, yy), zz); 90 | // return Point(x_result, y_result, t_result, z_result); 91 | // } 92 | 93 | // follows aztec protocol implementation 94 | fn double_point(p: Point) -> Point { 95 | var T0 = field_multiply(p.x, p.x); 96 | var T1 = field_multiply(p.y, p.y); 97 | var T2 = field_multiply(T1, T1); 98 | T1 = field_add(p.x, T1); 99 | T1 = field_multiply(T1, T1); 100 | var T3 = field_add(T0, T2); 101 | T1 = field_sub(T1, T3); 102 | T1 = field_double(T1); 103 | T3 = field_double(T0); 104 | T3 = field_add(T3, T0); 105 | var z_result = field_double(p.z); 106 | z_result = field_multiply(z_result, p.y); 107 | T0 = field_double(T1); 108 | var x_result = field_multiply(T3, T3); 109 | x_result = field_sub(x_result, T0); 110 | T2 = field_double(T2); 111 | T2 = field_double(T2); 112 | T2 = field_double(T2); 113 | var y_result = field_sub(T1, x_result); 114 | y_result = field_multiply(T3, y_result); 115 | y_result = field_sub(y_result, T2); 116 | var t_result = field_multiply(x_result, y_result); 117 | return Point(x_result, y_result, t_result, z_result); 118 | } 119 | 120 | fn normalize_x(x: Field, z: Field) -> Field { 121 | var z_inverse = field_inverse(z); 122 | var z_inv_squared = field_multiply(z_inverse, z_inverse); 123 | return field_multiply(x, z_inv_squared); 124 | } 125 | `; -------------------------------------------------------------------------------- /src/gpu/wgsl/BN254Params.ts: -------------------------------------------------------------------------------- 1 | export const BN254ParamsWGSL = 2 | ` 3 | // 21888242871839275222246405745257275088696311157297823662689037894645226208583 4 | const FIELD_ORDER: Field = Field( 5 | array(811880050, 3778125865, 3092268470, 2172737629, 2541841041, 1752287885, 1008765974, 3632069959) 6 | ); 7 | 8 | // 21888242871839275222246405745257275088696311157297823662689037894645226208584 9 | const FIELD_ORDER_PLUS_ONE: Field = Field( 10 | array(811880050, 3778125865, 3092268470, 2172737629, 2541841041, 1752287885, 1008765974, 3632069960) 11 | ); 12 | 13 | // 21888242871839275222246405745257275088696311157297823662689037894645226208582 14 | const FIELD_ORDER_MINUS_ONE: Field = Field( 15 | array(811880050, 3778125865, 3092268470, 2172737629, 2541841041, 1752287885, 1008765974, 3632069958) 16 | ); 17 | 18 | // 21888242871839275222246405745257275088696311157297823662689037894645226208583 19 | const p: Field = FIELD_ORDER; 20 | // 5472060717959818805561601436314318772174077789324455915672259473661306552146 21 | const p_plus_one_div_4: Field = Field( 22 | array(202970012, 3092015114, 1846808941, 2690668055, 1709202084, 1511813795, 1325933317, 3055501138) 23 | ); 24 | 25 | // assumes that num is indeed a square root residue. 26 | // follows the Shanks Tonelli algorithm. View shankstonelli.ts for the non-shortened version. 27 | fn field_sqrt(num: Field) -> Field { 28 | return field_pow(num, p_plus_one_div_4); 29 | } 30 | `; -------------------------------------------------------------------------------- /src/gpu/wgsl/Curve.ts: -------------------------------------------------------------------------------- 1 | export const CurveWGSL = ` 2 | struct AffinePoint { 3 | x: Field, 4 | y: Field 5 | } 6 | 7 | struct Point { 8 | x: Field, 9 | y: Field, 10 | t: Field, 11 | z: Field 12 | } 13 | 14 | struct MulPointIntermediate { 15 | result: Point, 16 | temp: Point, 17 | scalar: Field 18 | } 19 | 20 | fn mul_point_32_bit_scalar(p: Point, scalar: u32) -> Point { 21 | var result: Point = ZERO_POINT; 22 | var temp = p; 23 | var scalar_iter = scalar; 24 | while (!(scalar_iter == 0u)) { 25 | if ((scalar_iter & 1u) == 1u) { 26 | result = add_points(result, temp); 27 | } 28 | 29 | temp = double_point(temp); 30 | 31 | scalar_iter = scalar_iter >> 1u; 32 | } 33 | 34 | return result; 35 | } 36 | 37 | fn mul_point(p: Point, scalar: Field) -> Point { 38 | var result: Point = ZERO_POINT; 39 | var temp = p; 40 | var scalar_iter = scalar; 41 | while (!equal(scalar_iter, U256_ZERO)) { 42 | if (is_odd(scalar_iter)) { 43 | result = add_points(result, temp); 44 | } 45 | 46 | temp = double_point(temp); 47 | 48 | scalar_iter = u256_rs1(scalar_iter); 49 | } 50 | 51 | return result; 52 | } 53 | 54 | fn mul_point_64_bits_start(p: Point, scalar: Field) -> MulPointIntermediate { 55 | var result: Point = ZERO_POINT; 56 | var temp = p; 57 | var scalar_iter = scalar; 58 | for (var i = 0u; i < 64u; i = i + 1u) { 59 | if (equal(scalar_iter, U256_ZERO)) { 60 | break; 61 | } 62 | 63 | if (is_odd(scalar_iter)) { 64 | result = add_points(result, temp); 65 | } 66 | 67 | temp = double_point(temp); 68 | 69 | scalar_iter = u256_rs1(scalar_iter); 70 | } 71 | 72 | return MulPointIntermediate(result, temp, scalar_iter); 73 | } 74 | 75 | fn mul_point_64_bits(p: Point, scalar: Field, t: Point) -> MulPointIntermediate { 76 | if (equal(scalar, U256_ZERO)) { 77 | return MulPointIntermediate(p, t, scalar); 78 | } 79 | 80 | var result: Point = p; 81 | var temp = t; 82 | var scalar_iter = scalar; 83 | for (var i = 0u; i < 64u; i = i + 1u) { 84 | if (equal(scalar_iter, U256_ZERO)) { 85 | break; 86 | } 87 | 88 | if (is_odd(scalar_iter)) { 89 | result = add_points(result, temp); 90 | } 91 | 92 | temp = double_point(temp); 93 | 94 | scalar_iter = u256_rs1(scalar_iter); 95 | } 96 | 97 | return MulPointIntermediate(result, temp, scalar_iter); 98 | } 99 | 100 | fn mul_point_test(p: Point, scalar: Field) -> Point { 101 | var result: Point = ZERO_POINT; 102 | var temp = p; 103 | var scalar_iter = scalar; 104 | while (!equal(scalar_iter, U256_ZERO)) { 105 | if ((scalar_iter.components[7u] & 1u) == 1u) { 106 | var added = add_points(result, temp); 107 | result = added; 108 | } 109 | 110 | temp = double_point(temp); 111 | 112 | var right_shifted = u256_rs1(scalar_iter); 113 | scalar_iter = right_shifted; 114 | } 115 | 116 | return result; 117 | } 118 | 119 | // Additional required functions 120 | fn get_bits(x: Field, start: u32, length: u32) -> u32 { 121 | let bit_pos: u32 = start % 32; // Bit position within the u32 component 122 | let idx: u32 = 7 - (start / 32); // Index of the u32 component in the Field 123 | let mask: u32 = (1u << length) - 1u; // Create a mask with 'length' number of 1s 124 | // Extract the required u32 component and shift it so that the bits we're interested in are at the rightmost position 125 | let shifted: u32 = x.components[idx] >> bit_pos; 126 | // Apply the mask to isolate the bits we're interested in 127 | return shifted & mask; 128 | } 129 | 130 | fn mul_point_windowed(p: Point, scalar: Field) -> Point { 131 | // Pre-computation 132 | var precomputed: array = array(); 133 | precomputed[0] = ZERO_POINT; // Neutral element 134 | precomputed[1] = p; 135 | for (var i: u32 = 2; i < 16; i = i + 1) { 136 | precomputed[i] = add_points(precomputed[i - 1], p); 137 | } 138 | // Initialize result 139 | var result: Point = ZERO_POINT; // Neutral element 140 | // Calculate the number of windows 141 | let num_windows: u32 = (256 + 3) / 4; // number of bits divided by the window size, rounded up 142 | // Multiply 143 | for (var window: u32 = num_windows; window > 0; window = window - 1) { 144 | result = double_point(double_point(double_point(double_point(result)))); // Double it 4 times 145 | // Take the next 4 bits from scalar 146 | let bits: u32 = get_bits(scalar, (window - 1) * 4, 4); 147 | // Add the corresponding precomputed point 148 | result = add_points(result, precomputed[bits]); 149 | } 150 | return result; 151 | } 152 | 153 | // fn get_y(x: Field) -> Field { 154 | // var x_squared = field_multiply(x, x); 155 | // var numerator = field_sub(FIELD_ORDER_MINUS_ONE, x_squared); 156 | // var denominator = field_add(EDWARDS_D_PLUS_ONE, x_squared); 157 | // var denominator_inverse = field_inverse(denominator); 158 | // var y_squared = field_mul(numerator, denominator_inverse); 159 | // var y = field_sqrt(y_squared); 160 | // var neg_y = u256_sub(FIELD_ORDER, y); 161 | // } 162 | `; -------------------------------------------------------------------------------- /src/gpu/wgsl/FieldModulus.ts: -------------------------------------------------------------------------------- 1 | export const FieldModulusWGSL = 2 | ` 3 | alias Field = u256; 4 | 5 | fn field_reduce(a: u256) -> Field { 6 | var reduction: Field = a; 7 | var a_gte_ALEO = gte(a, FIELD_ORDER); 8 | 9 | while (a_gte_ALEO) { 10 | reduction = u256_sub(reduction, FIELD_ORDER); 11 | a_gte_ALEO = gte(reduction, FIELD_ORDER); 12 | } 13 | 14 | return reduction; 15 | } 16 | 17 | fn field_add(a: Field, b: Field) -> Field { 18 | var sum = u256_add(a, b); 19 | var result = field_reduce(sum); 20 | return result; 21 | } 22 | 23 | fn field_sub(a: Field, b: Field) -> Field { 24 | var sub: Field; 25 | if (gte(a, b)) { 26 | sub = u256_sub(a, b); 27 | } else { 28 | var b_minus_a: Field = u256_sub(b, a); 29 | sub = u256_sub(FIELD_ORDER, b_minus_a); 30 | } 31 | 32 | return sub; 33 | } 34 | 35 | fn field_double(a: Field) -> Field { 36 | var double = u256_double(a); 37 | var result = field_reduce(double); 38 | return result; 39 | } 40 | 41 | fn field_multiply(a: Field, b: Field) -> Field { 42 | var accumulator: Field = Field( 43 | array(0, 0, 0, 0, 0, 0, 0, 0) 44 | ); 45 | var newA: Field = a; 46 | var newB: Field = b; 47 | var count: u32 = 0u; 48 | 49 | while (gt(newB, U256_ZERO)) { 50 | if ((newB.components[7] & 1u) == 1u) { 51 | accumulator = u256_add(accumulator, newA); 52 | 53 | var accumulator_gte_ALEO = gte(accumulator, FIELD_ORDER); 54 | 55 | if (accumulator_gte_ALEO) { 56 | accumulator = u256_sub(accumulator, FIELD_ORDER); 57 | } 58 | 59 | } 60 | 61 | newA = u256_double(newA); 62 | newA = field_reduce(newA); 63 | newB = u256_right_shift(newB, 1u); 64 | count = count + 1u; 65 | } 66 | 67 | return accumulator; 68 | } 69 | 70 | fn field_pow(base: Field, exponent: Field) -> Field { 71 | if (equal(exponent, U256_ZERO)) { 72 | return U256_ONE; 73 | } 74 | 75 | if (equal(exponent, U256_ONE)) { 76 | return base; 77 | } 78 | 79 | var exp = exponent; 80 | var bse = base; 81 | var result: u256 = u256( 82 | array(0, 0, 0, 0, 0, 0, 0, 1) 83 | ); 84 | while (gt(exp, U256_ZERO)) { 85 | if (is_odd(exp)) { 86 | result = field_multiply(result, bse); 87 | } 88 | 89 | exp = u256_rs1(exp); 90 | bse = field_multiply(bse, bse); 91 | } 92 | 93 | return result; 94 | } 95 | 96 | fn field_pow_by_17(base: Field) -> Field { 97 | let bse = base; 98 | let base2 = field_multiply(bse, bse); 99 | let base4 = field_multiply(base2, base2); 100 | let base8 = field_multiply(base4, base4); 101 | let base16 = field_multiply(base8, base8); 102 | return field_multiply(base16, bse); 103 | } 104 | 105 | // assume that the input is NOT 0, as there's no inverse for 0 106 | // this function implements the Guajardo Kumar Paar Pelzl (GKPP) algorithm, 107 | // Algorithm 16 (BEA for inversion in Fp) 108 | fn field_inverse(num: Field) -> Field { 109 | var u: Field = num; 110 | var v: u256 = FIELD_ORDER; 111 | var b: Field = U256_ONE; 112 | var c: Field = U256_ZERO; 113 | 114 | while (!equal(u, U256_ONE) && !equal(v, U256_ONE)) { 115 | while (is_even(u)) { 116 | // divide by 2 117 | u = u256_rs1(u); 118 | 119 | if (is_even(b)) { 120 | // divide by 2 121 | b = u256_rs1(b); 122 | } else { 123 | b = u256_add(b, FIELD_ORDER); 124 | b = u256_rs1(b); 125 | } 126 | } 127 | 128 | while (is_even(v)) { 129 | // divide by 2 130 | v = u256_rs1(v); 131 | if (is_even(c)) { 132 | c = u256_rs1(c); 133 | } else { 134 | c = u256_add(c, FIELD_ORDER); 135 | c = u256_rs1(c); 136 | } 137 | } 138 | 139 | if (gte(u, v)) { 140 | u = u256_sub(u, v); 141 | b = field_sub(b, c); 142 | } else { 143 | v = u256_sub(v, u); 144 | c = field_sub(c, b); 145 | } 146 | } 147 | 148 | if (equal(u, U256_ONE)) { 149 | return field_reduce(b); 150 | } else { 151 | return field_reduce(c); 152 | } 153 | } 154 | 155 | fn gen_field_reduce(a: u256, field_order: u256) -> Field { 156 | var reduction: Field = a; 157 | var a_gte_field_order = gte(a, field_order); 158 | 159 | while (a_gte_field_order) { 160 | reduction = u256_sub(reduction, field_order); 161 | a_gte_field_order = gte(reduction, field_order); 162 | } 163 | 164 | return reduction; 165 | } 166 | 167 | fn gen_field_add(a: Field, b: Field, field_order: Field) -> Field { 168 | var sum = u256_add(a, b); 169 | var result = gen_field_reduce(sum, field_order); 170 | return result; 171 | } 172 | 173 | fn gen_field_sub(a: Field, b: Field, field_order: Field) -> Field { 174 | var sub: Field; 175 | if (gte(a, b)) { 176 | sub = u256_sub(a, b); 177 | } else { 178 | var b_minus_a: Field = u256_sub(b, a); 179 | sub = u256_sub(field_order, b_minus_a); 180 | } 181 | 182 | return sub; 183 | } 184 | 185 | fn gen_field_multiply(a: Field, b: Field, field_order: u256) -> Field { 186 | var accumulator: Field = Field( 187 | array(0, 0, 0, 0, 0, 0, 0, 0) 188 | ); 189 | var newA: Field = a; 190 | var newB: Field = b; 191 | while (gt(newB, U256_ZERO)) { 192 | if ((newB.components[7] & 1u) == 1u) { 193 | accumulator = u256_add(accumulator, newA); 194 | if (gte(accumulator, field_order)) { 195 | accumulator = u256_sub(accumulator, field_order); 196 | } 197 | } 198 | newA = u256_double(newA); 199 | if (gte(newA, field_order)) { 200 | newA = u256_sub(newA, field_order); 201 | } 202 | newB = u256_rs1(newB); 203 | } 204 | 205 | return accumulator; 206 | } 207 | 208 | fn gen_field_pow(base: Field, exponent: Field, field_order: u256) -> Field { 209 | if (equal(exponent, U256_ZERO)) { 210 | return U256_ONE; 211 | } 212 | 213 | if (equal(exponent, U256_ONE)) { 214 | return base; 215 | } 216 | 217 | var exp = exponent; 218 | var bse = base; 219 | var result: u256 = u256( 220 | array(0, 0, 0, 0, 0, 0, 0, 1) 221 | ); 222 | while (gt(exp, U256_ZERO)) { 223 | if (is_odd(exp)) { 224 | result = gen_field_multiply(result, bse, field_order); 225 | } 226 | 227 | exp = u256_rs1(exp); 228 | bse = gen_field_multiply(bse, bse, field_order); 229 | } 230 | 231 | return result; 232 | } 233 | ` -------------------------------------------------------------------------------- /src/gpu/wgsl/U256.ts: -------------------------------------------------------------------------------- 1 | export const U256WGSL = 2 | ` 3 | // big endian 4 | struct u256 { 5 | components: array 6 | } 7 | 8 | // 115792089237316195423570985008687907853269984665640564039457584007913129639935 9 | const U256_MAX: u256 = u256( 10 | array(4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295) 11 | ); 12 | 13 | const U256_SEVENTEEN: u256 = u256( 14 | array(0, 0, 0, 0, 0, 0, 0, 17) 15 | ); 16 | 17 | const U256_ONE: u256 = u256( 18 | array(0, 0, 0, 0, 0, 0, 0, 1) 19 | ); 20 | 21 | const U256_TWO: u256 = u256( 22 | array(0, 0, 0, 0, 0, 0, 0, 2) 23 | ); 24 | 25 | const U256_THREE: u256 = u256( 26 | array(0, 0, 0, 0, 0, 0, 0, 3) 27 | ); 28 | 29 | const U256_FOUR: u256 = u256( 30 | array(0, 0, 0, 0, 0, 0, 0, 4) 31 | ); 32 | 33 | const U256_EIGHT: u256 = u256( 34 | array(0, 0, 0, 0, 0, 0, 0, 8) 35 | ); 36 | 37 | const U256_ZERO: u256 = u256( 38 | array(0, 0, 0, 0, 0, 0, 0, 0) 39 | ); 40 | 41 | // adds u32s together, returns a vector of the result and the carry (either 0 or 1) 42 | fn add_components(a: u32, b: u32, carry_in: u32) -> vec2 { 43 | var sum: vec2; 44 | let total = a + b + carry_in; 45 | // potential bitwise speed ups here 46 | sum[0] = total; 47 | sum[1] = 0u; 48 | // if the total is less than a, then we know there was a carry 49 | // need to subtract the carry_in for the edge case though, where a or b is 2^32 - 1 and carry_in is 1 50 | if (total < a || (total - carry_in) < a) { 51 | sum[1] = 1u; 52 | } 53 | return sum; 54 | } 55 | 56 | // subtracts u32s together, returns a vector of the result and the carry (either 0 or 1) 57 | fn sub_components(a: u32, b: u32, carry_in: u32) -> vec2 { 58 | var sub: vec2; 59 | let total = a - b - carry_in; 60 | sub[0] = total; 61 | sub[1] = 0u; 62 | // if the total is greater than a, then we know there was a carry from a less significant component. 63 | // need to add the carry_in for the edge case though, where a carry_in of 1 causes a component of a to underflow 64 | if (total > a || (total + carry_in) > a) { 65 | sub[1] = 1u; 66 | } 67 | return sub; 68 | } 69 | 70 | // no overflow checking for u256 71 | fn u256_add(a: u256, b: u256) -> u256 { 72 | var sum: u256; 73 | sum.components = array(0, 0, 0, 0, 0, 0, 0, 0); 74 | var carry: u32 = 0u; 75 | 76 | for (var i = 7i; i >= 0i; i--) { 77 | let componentResult = add_components(a.components[i], b.components[i], carry); 78 | sum.components[i] = componentResult[0]; 79 | carry = componentResult[1]; 80 | } 81 | 82 | return sum; 83 | } 84 | 85 | fn u256_rs1(a: u256) -> u256 { 86 | var right_shifted: u256 = u256 ( 87 | array(0, 0, 0, 0, 0, 0, 0, 0) 88 | ); 89 | var carry: u32 = 0u; 90 | for (var i = 0u; i < 8u; i++) { 91 | var componentResult = a.components[i] >> 1u; 92 | componentResult = componentResult | carry; 93 | right_shifted.components[i] = componentResult; 94 | carry = a.components[i] << 31u; 95 | } 96 | 97 | return right_shifted; 98 | } 99 | 100 | fn is_even(a: u256) -> bool { 101 | return (a.components[7u] & 1u) == 0u; 102 | } 103 | 104 | fn is_odd(a: u256) -> bool { 105 | return (a.components[7u] & 1u) == 1u; 106 | } 107 | 108 | fn is_odd_32_bits(a: u32) -> bool { 109 | return (a & 1u) == 1u; 110 | } 111 | 112 | // no underflow checking for u256 113 | fn u256_sub(a: u256, b: u256) -> u256 { 114 | var sub: u256; 115 | sub.components = array(0, 0, 0, 0, 0, 0, 0, 0); 116 | var carry: u32 = 0u; 117 | for (var i = 7i; i >= 0i; i--) { 118 | let componentResult = sub_components(a.components[i], b.components[i], carry); 119 | sub.components[i] = componentResult[0]; 120 | carry = componentResult[1]; 121 | } 122 | 123 | return sub; 124 | } 125 | 126 | // underflow allowed u256 subtraction 127 | fn u256_subw(a: u256, b: u256) -> u256 { 128 | var sub: u256; 129 | if (gte(a, b)) { 130 | sub = u256_sub(a, b); 131 | } else { 132 | var b_minus_a: u256 = u256_sub(b, a); 133 | var b_minus_a_minus_one: u256 = u256_sub(b_minus_a, U256_ONE); 134 | sub = u256_sub(U256_MAX, b_minus_a_minus_one); 135 | } 136 | 137 | return sub; 138 | } 139 | 140 | fn equal(a: u256, b: u256) -> bool { 141 | for (var i = 0u; i < 8u; i++) { 142 | if (a.components[i] != b.components[i]) { 143 | return false; 144 | } 145 | } 146 | 147 | return true; 148 | } 149 | 150 | // returns whether a > b 151 | fn gt(a: u256, b: u256) -> bool { 152 | for (var i = 0u; i < 8u; i++) { 153 | if (a.components[i] != b.components[i]) { 154 | return a.components[i] > b.components[i]; 155 | } 156 | } 157 | // if a's components are never greater, than a is equal to b 158 | return false; 159 | } 160 | 161 | // returns whether a >= b 162 | fn gte(a: u256, b: u256) -> bool { 163 | for (var i = 0u; i < 8u; i++) { 164 | if (a.components[i] != b.components[i]) { 165 | return a.components[i] > b.components[i]; 166 | } 167 | } 168 | // if none of the components are greater or smaller, a is equal to b 169 | return true; 170 | } 171 | 172 | fn component_double(a: u32, carry: u32) -> vec2 { 173 | var double: vec2; 174 | let total = a << 1u; 175 | 176 | double[0] = total + carry; 177 | double[1] = 0u; 178 | 179 | if (total < a) { 180 | double[1] = 1u; 181 | } 182 | return double; 183 | } 184 | 185 | fn u256_double(a: u256) -> u256 { 186 | var double: u256; 187 | double.components = array(0, 0, 0, 0, 0, 0, 0, 0); 188 | var carry: u32 = 0u; 189 | 190 | for (var i = 7i; i >= 0i; i--) { 191 | let componentResult = component_double(a.components[i], carry); 192 | double.components[i] = componentResult[0]; 193 | carry = componentResult[1]; 194 | } 195 | 196 | return double; 197 | } 198 | 199 | fn component_right_shift(a: u32, shift: u32, carry: u32) -> vec2 { 200 | var shifted: vec2; 201 | shifted[0] = (a >> shift) + carry; 202 | shifted[1] = a << (32u - shift); 203 | 204 | return shifted; 205 | } 206 | 207 | fn u256_right_shift(a: u256, shift: u32) -> u256 { 208 | var components_to_drop = shift / 32u; 209 | if (components_to_drop >= 8u) { 210 | return U256_ZERO; 211 | } 212 | 213 | var big_shift: u256 = u256( 214 | array(0, 0, 0, 0, 0, 0, 0, 0) 215 | ); 216 | 217 | // Shift out the components that need dropping 218 | for (var i = components_to_drop; i < 8u; i++) { 219 | big_shift.components[i] = a.components[i-components_to_drop]; 220 | } 221 | 222 | var shift_within_component = shift % 32u; 223 | 224 | if (shift_within_component == 0u) { 225 | return big_shift; 226 | } 227 | 228 | var carry: u32 = 0u; 229 | for (var i = components_to_drop; i < 8u; i++) { 230 | let componentResult = component_right_shift(big_shift.components[i], shift_within_component, carry); 231 | big_shift.components[i] = componentResult[0]; 232 | carry = componentResult[1]; 233 | } 234 | 235 | return big_shift; 236 | } 237 | `; -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebGPU Crypto 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import React from "react"; 3 | import ReactDOM from "react-dom"; 4 | import GPUFunctionLoader from "./GPUFunctionLoader"; 5 | import './main.css'; 6 | import { AllBenchmarks } from "./ui/AllBenchmarks"; 7 | 8 | const App = () => ( 9 | <> 10 |

WebGPU Crypto {new Date().toLocaleDateString()}

11 | 12 | 13 | 14 | ); 15 | 16 | ReactDOM.render( 17 | 18 | 19 | , 20 | document.getElementById("root") 21 | ); 22 | -------------------------------------------------------------------------------- /src/main.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; -------------------------------------------------------------------------------- /src/params/BLS12_377Constants.ts: -------------------------------------------------------------------------------- 1 | // base field modulus https://docs.rs/ark-ed-on-bls12-377/latest/ark_ed_on_bls12_377/ 2 | // https://github.com/AleoHQ/snarkVM/tree/testnet3/curves 3 | export const FIELD_MODULUS = BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239041'); 4 | export const EDWARDS_A = BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239040'); 5 | export const EDWARDS_D = BigInt('3021'); 6 | export const SUBGROUP_CHARACTERISTIC = BigInt('2111115437357092606062206234695386632838870926408408195193685246394721360383'); 7 | export const ROOT_OF_UNITY = BigInt('5928890464389279575069867463136436689218492512582288454256978381122364252082'); 8 | export const ROOTS_OF_UNITY: { [index: number]: bigint; } = { 9 | 0 : BigInt('1'), 10 | 1 : BigInt('8444461749428370424248824938781546531375899335154063827935233455917409239040'), // 313222494, 2586617174, 1622428958, 1547153409, 1504343806, 3489660929, 168919040, 0 11 | 2 : BigInt('880904806456922042258150504921383618666682042621506879489'), // 0, 0, 602739527, 2534272000, 3479586661, 2952790017, 2400862208, 1 12 | 3 : BigInt('3279917132858342911831074864712036382710139745724269329239664300762234227201'), // 121658888, 126410241, 1896398104, 4032647219, 553170028, 3612830112, 260247422, 4227467777 13 | 4 : BigInt('6638943125734816116533668954109782743742964348031428195244516495664415882670'), // 246252086, 3265874006, 167946269, 3465008736, 823585826, 2022741237, 3347088806, 1622785454 14 | 5 : BigInt('2044129581458360471194413030819022067476444176988436074573508567144719427532'), // 75820980, 702117846, 3259115677, 194177225, 293443950, 2508851828, 1878800857, 1048815564 15 | 6 : BigInt('6821963945538064897924581611421472144776078914389058187104145641528714456127'), // 253040706, 1140641062, 4240048997, 863972615, 1801639855, 3321282159, 356866816, 17951807 16 | 7 : BigInt('2252774523953722881297536424069691740647177044028921592024459201778483644059'), // 83560051, 874501581, 2517297994, 3476756874, 1040862183, 708300319, 1469389614, 3273574043 17 | 8 : BigInt('3007047394428189914303468568563069309375399555829781313999234300434804999455'), 18 | 9 : BigInt('7230648057878268440511611882767992485465416891455657199765901381639068805022'), 19 | 10 : BigInt('4443108021371737602697596971902852132274961263160868774409420424726835935922'), 20 | 11 : BigInt('866985245235040709753013773884295437521109261361027824514873427502964982922'), 21 | 12 : BigInt('7010854306464939002932811365735081035812768059550215077065386543336384326022'), 22 | 13 : BigInt('7070812726325190633410472016297970366797562740797297497724343127078610912889'), 23 | 14 : BigInt('2220072868610258395328271154471256313557670712203832739820566102212527215391'), 24 | 15 : BigInt('7591293405427898564870858826610817218034369597505373240686046426103999208668'), 25 | 16 : BigInt('2952730183556248050906521597191761902300071509314809436739482176595887578715'), 26 | 17 : BigInt('6559097684151601200296380261499015583126113930924043519043891675822901590271'), 27 | 18 : BigInt('4182993634352617393774568683755264101301576136097235699190248620166686863232'), // 155155857, 722716680, 1446323039, 2438065132, 1204949397, 275317037, 457929167, 1031171968 28 | 19 : BigInt('3416361906115579384928097798765057187399728387276024011557036022219423576093'), // 126719905, 4086150706, 1214205519, 251008842, 2322923638, 1283675055, 139000152, 1178038301 29 | 20 : BigInt('5806138679692263254121574581997772257156815907370451271750339947304134469737'), 30 | 21 : BigInt('1090667085228256523692758215825261203193836959638452625274363081031742450469'), 31 | 22 : BigInt('7382056626524619294931792862821426889273799308092906887983073139275863922209'), 32 | 23 : BigInt('6608629675501719378966964710849395262473973268956867731613072317575458992168'), 33 | 24 : BigInt('5421008228431423068756151873766382091821790348027112600470148769114500057419'), 34 | 25 : BigInt('3529014555435592171742066817139573121393358263191449113732825556450770560707'), 35 | 26 : BigInt('2910765783884067641018326899470073510450179822676431422756433923313788232748'), 36 | 27 : BigInt('4747820667816889455130704068848313584804747290628016298973913312963941923422'), 37 | 28 : BigInt('121723987773120995826174558753991820398372762714909599931916967971437184138'), 38 | 29 : BigInt('5397927263010650931348011612970043207219523113361339973520729239827269495178'), 39 | 30 : BigInt('6233809076977653298485922423599990267184617816269486526444677433380117346298'), 40 | 31 : BigInt('4339062137086360334807674201436922008994516220599126170216127120322318257453'), 41 | } -------------------------------------------------------------------------------- /src/params/BN254Constants.ts: -------------------------------------------------------------------------------- 1 | export const FR_FIELD_MODULUS = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617'); 2 | export const ROOTS_OF_UNITY = { 3 | 0 : BigInt('1'), 4 | 1 : BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495616'), 5 | 2 : BigInt('21888242871839275217838484774961031246007050428528088939761107053157389710902'), 6 | 3 : BigInt('19540430494807482326159819597004422086093766032135589407132600596362845576832'), 7 | 4 : BigInt('14940766826517323942636479241147756311199852622225275649687664389641784935947'), 8 | 5 : BigInt('4419234939496763621076330863786513495701855246241724391626358375488475697872'), 9 | 6 : BigInt('9088801421649573101014283686030284801466796108869023335878462724291607593530'), 10 | 7 : BigInt('10359452186428527605436343203440067497552205259388878191021578220384701716497'), 11 | 8 : BigInt('3478517300119284901893091970156912948790432420133812234316178878452092729974'), 12 | 9 : BigInt('6837567842312086091520287814181175430087169027974246751610506942214842701774'), 13 | 10 : BigInt('3161067157621608152362653341354432744960400845131437947728257924963983317266'), 14 | 11 : BigInt('1120550406532664055539694724667294622065367841900378087843176726913374367458'), 15 | 12 : BigInt('4158865282786404163413953114870269622875596290766033564087307867933865333818'), 16 | 13 : BigInt('197302210312744933010843010704445784068657690384188106020011018676818793232'), 17 | 14 : BigInt('20619701001583904760601357484951574588621083236087856586626117568842480512645'), 18 | 15 : BigInt('20402931748843538985151001264530049874871572933694634836567070693966133783803'), 19 | 16 : BigInt('421743594562400382753388642386256516545992082196004333756405989743524594615'), 20 | 17 : BigInt('12650941915662020058015862023665998998969191525479888727406889100124684769509'), 21 | 18 : BigInt('11699596668367776675346610687704220591435078791727316319397053191800576917728'), 22 | 19 : BigInt('15549849457946371566896172786938980432421851627449396898353380550861104573629'), 23 | 20 : BigInt('17220337697351015657950521176323262483320249231368149235373741788599650842711'), 24 | 21 : BigInt('13536764371732269273912573961853310557438878140379554347802702086337840854307'), 25 | 22 : BigInt('12143866164239048021030917283424216263377309185099704096317235600302831912062'), 26 | 23 : BigInt('934650972362265999028062457054462628285482693704334323590406443310927365533'), 27 | 24 : BigInt('5709868443893258075976348696661355716898495876243883251619397131511003808859'), 28 | } 29 | // fr modulus 30 | // export const FIELD_MODULUS = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617'); 31 | // fq modulus 32 | export const FQ_FIELD_MODULUS = BigInt('21888242871839275222246405745257275088696311157297823662689037894645226208583'); 33 | -------------------------------------------------------------------------------- /src/parsers/aleo/AddressParser.test.ts: -------------------------------------------------------------------------------- 1 | import { convertXCoordinateToAddress, parseAddressToXCoordinate, parseViewKeyToScalar } from "./AddressParser"; 2 | 3 | describe('AddressParser', () => { 4 | describe('parseAddressToXCoordinate', () => { 5 | it.each([ 6 | [ 7 | 'aleo1xr8udktvjhhackktfldsygjuf84fpm5k377gw4dcxhswtnyt8urq2q7xtv', 8 | '2826153323360709902210507492352501050408684853149149403539658328382212853552' 9 | ], 10 | [ 11 | 'aleo1szk6r8y2nmhwnd5y3u8tlsm20u6n4cwqwzyexe4vvmfvcrfk0cqs3q0lgh', 12 | '675308645097637656805864707926756940717984767614837689901657352478681836928' 13 | ], 14 | [ 15 | 'aleo1nn2htg0tcf2j4jxmdqlt8x7ln23wav938r3glernsqt83u3c5czq2mmd3p', 16 | '2102941041835522765063869490308094100677831793826356135439686213042972251548' 17 | ], 18 | [ 19 | 'aleo1xr8udktvjhhackktfldsygjuf84fpm5k377gw4dcxhswtnyt8urq2q7xtv', 20 | '2826153323360709902210507492352501050408684853149149403539658328382212853552' 21 | ], 22 | [ 23 | 'aleo14rwswfzqwmtym372kd2amu7jzw6xmkz38gwdg9j6thv9g544kvgqhxu2s2', 24 | '7554522637667228564357761306560014769896981682593547110129454045518044323240' 25 | ] 26 | ])('should return the x coordinate for an address', (address: string, expectedXCoordinate: string) => { 27 | const xCoordinate = parseAddressToXCoordinate(address); 28 | expect(xCoordinate.toString()).toEqual(expectedXCoordinate); 29 | }); 30 | }); 31 | 32 | describe('parseViewKeyToXCoordinate', () => { 33 | it.each([ 34 | [ 35 | 'AViewKey1dS9uE4XrARX3m5QUDWSrqmUwxY3PFKVdMvPwzbtbYrUh', 36 | '1047782004112991658538528321810337177976429471185056028001320450422875039246' 37 | ], 38 | [ 39 | 'AViewKey1sbprcE2wG2nh1kYbDLU42FFKmGnxxKUq8oNsMaAU9ByS', 40 | '752209293482294422588181595597784470577861263737725847909891016010783237344' 41 | ] 42 | ])('should return the x coordinate for an viewKey', (viewKey: string, expectedXCoordinate: string) => { 43 | const xCoordinate = parseViewKeyToScalar(viewKey); 44 | expect(xCoordinate.toString()).toEqual(expectedXCoordinate); 45 | }); 46 | }); 47 | 48 | describe('convertXCoordinateToAddress', () => { 49 | it.each([ 50 | [ 51 | 'aleo1xr8udktvjhhackktfldsygjuf84fpm5k377gw4dcxhswtnyt8urq2q7xtv', 52 | '2826153323360709902210507492352501050408684853149149403539658328382212853552' 53 | ], 54 | [ 55 | 'aleo1szk6r8y2nmhwnd5y3u8tlsm20u6n4cwqwzyexe4vvmfvcrfk0cqs3q0lgh', 56 | '675308645097637656805864707926756940717984767614837689901657352478681836928' 57 | ], 58 | [ 59 | 'aleo1nn2htg0tcf2j4jxmdqlt8x7ln23wav938r3glernsqt83u3c5czq2mmd3p', 60 | '2102941041835522765063869490308094100677831793826356135439686213042972251548' 61 | ], 62 | [ 63 | 'aleo1xr8udktvjhhackktfldsygjuf84fpm5k377gw4dcxhswtnyt8urq2q7xtv', 64 | '2826153323360709902210507492352501050408684853149149403539658328382212853552' 65 | ] 66 | ])('should return the x coordinate for an address', (expectedAddress: string, XCoordinate: string) => { 67 | const address = convertXCoordinateToAddress(XCoordinate); 68 | expect(address).toEqual(expectedAddress); 69 | }); 70 | }); 71 | }); -------------------------------------------------------------------------------- /src/parsers/aleo/AddressParser.ts: -------------------------------------------------------------------------------- 1 | import { bech32m } from "bech32"; 2 | import BN from "bn.js"; 3 | import bs58 from "bs58"; 4 | 5 | export const parseAddressToXCoordinate = (address: string): bigint => { 6 | const bytes = parseAddressToBytes(address); 7 | return BigInt(convertBytesToFieldElement(bytes)); 8 | } 9 | 10 | export const parseViewKeyToScalar = (viewKey: string): bigint => { 11 | let bytes = bs58.decode(viewKey); 12 | bytes = bytes.slice(7); 13 | return BigInt(convertBytesToFieldElement(bytes)); 14 | } 15 | 16 | const parseAddressToBytes = (address: string): Uint8Array => { 17 | const ADDRESS_PREFIX = "aleo"; 18 | // Ensure the address string length is 63 characters. 19 | if (address.length !== 63) { 20 | throw new Error(`Invalid account address length: found ${address.length}, expected 63`); 21 | } 22 | 23 | // Decode the address string from bech32m. 24 | const { prefix, words: data } = bech32m.decode(address); 25 | 26 | if (prefix !== ADDRESS_PREFIX) { 27 | throw new Error(`Failed to decode address: '${prefix}' is an invalid prefix`); 28 | } else if (data.length === 0) { 29 | throw new Error("Failed to decode address: data field is empty"); 30 | } 31 | 32 | const u8Data = bech32m.fromWords(data); 33 | // Decode the address data from u5 to u8, and into an account address. 34 | return new Uint8Array(u8Data); 35 | } 36 | 37 | export const convertBytesToFieldElement = (bytes: Uint8Array): string => { 38 | const fieldElement = new BN(bytes, 16, 'le'); 39 | return fieldElement.toString(); 40 | } 41 | 42 | const convertFieldElementToBytes = (fieldElement: string): Uint8Array => { 43 | const fieldElementBN = new BN(fieldElement, 10, 'le'); 44 | return new Uint8Array(fieldElementBN.toArray()); 45 | } 46 | 47 | const parseBytesToAddress = (bytes: Uint8Array): string => { 48 | const words = bech32m.toWords(bytes); 49 | return bech32m.encode("aleo", words); 50 | } 51 | 52 | export const convertXCoordinateToAddress = (fieldElement: string): string => { 53 | const bytes = convertFieldElementToBytes(fieldElement); 54 | return parseBytesToAddress(bytes); 55 | } -------------------------------------------------------------------------------- /src/parsers/aleo/RecordParser.test.ts: -------------------------------------------------------------------------------- 1 | import { convertCiphertextToDataView, getNonce, isOwnerPublic } from "./RecordParser"; 2 | 3 | describe('RecordParser', () => { 4 | describe('convertCiphertextToDataView', () => { 5 | it('should convert a string to a DataView', () => { 6 | const ciphertext = 'record1qp90y0z0glrncvwadu7hk5na0utg9sc33lj2a7hh3l7wdnph7gtsgq9lm6e2c6yxpgqqyqtpgvqqyqsqv8fwzmg2ha6pm0drmu29jeu96mjnlyupf5v206pqdyjx2e5c7y8z8cwx90k0f2huqw3dfg7qc7rumh40x4p9mrrmc9ja50h5l6y9xrspvfpsqqszqzczsqnx404dy4r0fl4skj5q70zhav6q6366s2nh8y5934mtcvgspm6mussehflx2yrukk6nypdldksenkcwlp8xqtsjxqscmytdlcgvt4yfpvsd6sz2z94uxl9mfn8ympak7fj4z7umfmwlt955es7tqqfq629spq'; 7 | 8 | const dataView = convertCiphertextToDataView(ciphertext); 9 | 10 | expect(dataView.byteLength).toBe(217); 11 | }); 12 | }); 13 | 14 | describe('isOwnerPublic', () => { 15 | it('should return true for a public owned record', () => { 16 | const ciphertext = 'record1qp90y0z0glrncvwadu7hk5na0utg9sc33lj2a7hh3l7wdnph7gtsgq9lm6e2c6yxpgqqyqtpgvqqyqsqv8fwzmg2ha6pm0drmu29jeu96mjnlyupf5v206pqdyjx2e5c7y8z8cwx90k0f2huqw3dfg7qc7rumh40x4p9mrrmc9ja50h5l6y9xrspvfpsqqszqzczsqnx404dy4r0fl4skj5q70zhav6q6366s2nh8y5934mtcvgspm6mussehflx2yrukk6nypdldksenkcwlp8xqtsjxqscmytdlcgvt4yfpvsd6sz2z94uxl9mfn8ympak7fj4z7umfmwlt955es7tqqfq629spq'; 17 | const dataView = convertCiphertextToDataView(ciphertext); 18 | 19 | const publicOwner = isOwnerPublic(dataView); 20 | 21 | expect(publicOwner).toBe(true); 22 | }); 23 | 24 | it('should return false for a private owned record', () => { 25 | const ciphertext = 'record1qyqsplgtkd7mulyahq0ghzfkre898uzglr0t8s2mmxl4c4n8q8c5e5q8qptkz0tv3mwqxqqzq9s5xqqzqgqrvz5z8ywh56nw084z94m0thm5v2kcdlwwnsg0dy5fnj8xgedmuzl5stummk6m6u28hky5x24k4ypfjtmp5rdax4gw4dnaw5kgf20dpgqkyscqqgpqqqhqjf5r3fexvd083xvx07csxcj47dhxz25thrkrwk7ucxfuhqc2s3ze2sw3e5nwvx229f9qnmzz55umc247u68pudqv44cwu6lzwqrlc4p7pkmhkkkaa6zrj469cz9qynu0lklejulfu3grry7wpht0szga6s39c'; 26 | const dataView = convertCiphertextToDataView(ciphertext); 27 | 28 | const publicOwner = isOwnerPublic(dataView); 29 | 30 | expect(publicOwner).toBe(false); 31 | }); 32 | }); 33 | 34 | describe('getNonce', () => { 35 | it('should return the nonce for a record', () => { 36 | const ciphertext = 'record1qrcgv7yd68ryet69ec50shjcl54rqa7ghsxzs22k7yetxsgfq7rsqqp5u4gaqaplq5qqyqtpgvqqyqsqzafa6agrqh97flqc0vkj4hn92jwy8jfqd799ygphg77n2cldvvp0s0hdf4fym0zedvhzs2cup540ydslksfngkzv7qeuhe7j5x6cvqqpvfpsqqszqpvucnr8asgwfp2vpg2upncvumjz07ltvg9r0kslulgwpne0w9kqnpj469qdj77ztzv5frw640f76qs2und5fqlgp3qsc43xkdjfyzcfpln00ldkrdmrljajfvheq80ncc7jtuxkvvet2zjx2958l4pz0yxq6v054n'; 37 | const dataView = convertCiphertextToDataView(ciphertext); 38 | const expectedNonce = '5641783066139440241502253189471532734416076078221191010077573525863906403855'; 39 | 40 | const nonce = getNonce(dataView); 41 | 42 | expect(nonce.toString()).toBe(expectedNonce); 43 | }); 44 | }); 45 | }); -------------------------------------------------------------------------------- /src/parsers/aleo/RecordParser.ts: -------------------------------------------------------------------------------- 1 | import { bech32m } from "bech32"; 2 | import BN from "bn.js"; 3 | 4 | export const FIELD_BYTE_SIZE = 32; 5 | 6 | export const convertCiphertextToDataView = (ciphertext: string): DataView => { 7 | const RECORD_PREFIX = 'record'; 8 | const { prefix, words: data } = bech32m.decode(ciphertext, Infinity); 9 | if (prefix !== RECORD_PREFIX) { 10 | throw new Error(`Failed to decode address: '${prefix}' is an invalid prefix`); 11 | } else if (data.length === 0) { 12 | throw new Error("Failed to decode address: data field is empty"); 13 | } 14 | 15 | const u8Data = bech32m.fromWords(data); 16 | 17 | const bytes = new Uint8Array(u8Data); 18 | 19 | return new DataView(bytes.buffer); 20 | } 21 | 22 | export const getNonce = (dataView: DataView): bigint => { 23 | const nonceOffset = dataView.byteLength - FIELD_BYTE_SIZE; 24 | const nonceFieldBytes = readFieldBytes(dataView, nonceOffset); 25 | const nonceField = BigInt(convertBytesToFieldElement(nonceFieldBytes)); 26 | return nonceField; 27 | } 28 | 29 | export const readFieldBytes = (dataView: DataView, byteOffset: number): Uint8Array => { 30 | const fieldBytes = new Uint8Array(FIELD_BYTE_SIZE); 31 | for (let i = 0; i < FIELD_BYTE_SIZE; i++) { 32 | fieldBytes[i] = (dataView.getUint8(byteOffset)); 33 | byteOffset += 1; 34 | } 35 | return fieldBytes 36 | } 37 | 38 | export const convertBytesToFieldElement = (bytes: Uint8Array): string => { 39 | const fieldElement = new BN(bytes, 16, 'le'); 40 | return fieldElement.toString(); 41 | } 42 | 43 | export const isOwnerPublic = (ciphertextData: DataView): boolean => { 44 | // first byte determines public vs private. Public is 0, private is 1. 45 | return ciphertextData.getUint8(0) === 0; 46 | } 47 | 48 | export const getPublicOwnerBytes = (ciphertextData: DataView): Uint8Array => { 49 | const byteOffset = 1; // the first byte is the owner type 50 | const ownerFieldBytes = readFieldBytes(ciphertextData, byteOffset); 51 | return ownerFieldBytes; 52 | } 53 | 54 | export const getPrivateOwnerBytes = (ciphertextData: DataView): Uint8Array => { 55 | const byteOffset = 3; // the first byte is the owner type, the second & third bytes are the number of fields 56 | const ownerFieldBytes = readFieldBytes(ciphertextData, byteOffset); 57 | return ownerFieldBytes; 58 | } -------------------------------------------------------------------------------- /src/ui/Accordion.tsx: -------------------------------------------------------------------------------- 1 | // BenchmarkAccordion.tsx 2 | import React, { useState } from 'react'; 3 | 4 | interface AccordionProps { 5 | title: string; 6 | children: React.ReactNode; 7 | } 8 | 9 | export const Accordion: React.FC = ({ title, children }) => { 10 | const [isOpen, setIsOpen] = useState(false); 11 | 12 | return ( 13 |
14 | 17 | {isOpen && ( 18 |
19 | {children} 20 |
21 | )} 22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/ui/Benchmark.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React, { useEffect, useState } from 'react'; 4 | import { gpuU32Inputs, u32ArrayToBigInts } from '../gpu/utils'; 5 | import CSVExportButton from './CSVExportButton'; 6 | 7 | interface BenchmarkProps { 8 | name: string; 9 | inputsGenerator: (inputSize: number) => any[][] | Promise; 10 | gpuFunc: (inputs: any[], batchSize?: any) => Promise; 11 | gpuInputConverter: (inputs: any[][]) => gpuU32Inputs[] | bigint[][]; 12 | gpuResultConverter?: (results: bigint[]) => string[]; 13 | wasmFunc: (inputs: any[][]) => Promise; 14 | wasmInputConverter?: (inputs: any[][]) => any[][]; 15 | wasmResultConverter?: (results: string[]) => string[]; 16 | batchable: boolean; 17 | } 18 | 19 | export const Benchmark: React.FC = ( 20 | {name, inputsGenerator, gpuFunc, gpuInputConverter, gpuResultConverter, wasmFunc, wasmInputConverter, wasmResultConverter, batchable=true} 21 | ) => { 22 | const initialDefaultInputSize = 1_000; 23 | const [inputSize, setInputSize] = useState(initialDefaultInputSize); 24 | const initialBatchSize = 1_000_000; 25 | const [batchSize, setBatchSize] = useState(initialBatchSize); 26 | const [gpuTime, setGpuTime] = useState(0); 27 | const [wasmTime, setWasmTime] = useState(0); 28 | const [gpuRunning, setGpuRunning] = useState(false); 29 | const [wasmRunning, setWasmRunning] = useState(false); 30 | const initialResults: string[] = []; 31 | const [gpuResults, setGpuResults] = useState(initialResults); 32 | const [wasmResults, setWasmResults] = useState(initialResults); 33 | const [inputs, setInputs] = useState([]); 34 | const [comparison, setComparison] = useState('Run both GPU and WASM to compare results'); 35 | const [benchmarkResults, setBenchmarkResults] = useState([["InputSize", "GPUorWASM", "Time"]]); 36 | 37 | useEffect(() => { 38 | const fetchInputs = async () => { 39 | const generatedInputs = await inputsGenerator(inputSize); 40 | setInputs(generatedInputs); 41 | }; 42 | fetchInputs(); 43 | }, []); 44 | 45 | useEffect(() => { 46 | const setNewInputs = async () => { 47 | const newInputs = await inputsGenerator(inputSize); 48 | setInputs(newInputs); 49 | }; 50 | setNewInputs(); 51 | }, [inputSize]); 52 | 53 | useEffect(() => { 54 | if (gpuResults.length === 0 && wasmResults.length === 0) { 55 | setComparison('Run benchmarks to compare results.') 56 | } else if (gpuResults.length === 0) { 57 | setComparison('🎮 Run GPU') 58 | } else if (wasmResults.length === 0) { 59 | setComparison('👾 Run WASM') 60 | } else if (gpuResults.length !== wasmResults.length) { 61 | setComparison('⛔️ Different length results'); 62 | } else { 63 | let gpuResultsDiffIndex = -1; 64 | for (let i = 0; i < gpuResults.length; i++) { 65 | if (gpuResults[i] !== wasmResults[i]) { 66 | gpuResultsDiffIndex = i; 67 | break; 68 | } 69 | } 70 | if (gpuResultsDiffIndex !== -1) { 71 | setComparison(`❌ different at index ${gpuResultsDiffIndex}`); 72 | } else { 73 | setComparison('✅'); 74 | } 75 | } 76 | }, [gpuResults, wasmResults]); 77 | 78 | const runWasm = async () => { 79 | const wasmInputs = wasmInputConverter ? wasmInputConverter(inputs) : inputs; 80 | setWasmRunning(true); 81 | const wasmStart = performance.now(); 82 | const results: string[] = await wasmFunc(wasmInputs); 83 | const wasmEnd = performance.now(); 84 | const wasmPerformance = wasmEnd - wasmStart; 85 | setWasmTime(wasmPerformance); 86 | const comparableWasmResults = wasmResultConverter ? wasmResultConverter(results) : results; 87 | console.log(comparableWasmResults); 88 | setWasmResults(comparableWasmResults); 89 | const benchMarkResult = [inputSize, "WASM", wasmPerformance]; 90 | setBenchmarkResults([...benchmarkResults, benchMarkResult]); 91 | setWasmRunning(false); 92 | }; 93 | 94 | const runGpu = async () => { 95 | const gpuInputs = gpuInputConverter(inputs); 96 | setGpuRunning(true); 97 | const gpuStart = performance.now(); 98 | const result = await gpuFunc(gpuInputs, batchSize); 99 | const gpuEnd = performance.now(); 100 | const gpuPerformance = gpuEnd - gpuStart; 101 | setGpuTime(gpuPerformance); 102 | const bigIntResult = u32ArrayToBigInts(result || new Uint32Array(0)); 103 | const results = gpuResultConverter ? gpuResultConverter(bigIntResult) : bigIntResult.map(r => r.toString()); 104 | console.log(results); 105 | setGpuResults(results); 106 | const benchMarkResult = [inputSize, "GPU", gpuPerformance]; 107 | setBenchmarkResults([...benchmarkResults, benchMarkResult]); 108 | setGpuRunning(false); 109 | }; 110 | 111 | const spin = () =>
; 112 | 113 | return ( 114 |
115 |
{name}
116 |
Input Size:
117 | setInputSize(parseInt(e.target.value))} 122 | /> 123 |
Batch Size:
124 | setBatchSize(parseInt(e.target.value))} 129 | disabled={!batchable} /> 130 | 131 | 134 |
{gpuTime > 0 ? gpuTime : 'GPU Time: 0ms'}
135 | 138 |
{wasmTime > 0 ? wasmTime : 'WASM Time: 0ms'}
139 |
{comparison}
140 | 141 |
142 | ); 143 | }; -------------------------------------------------------------------------------- /src/ui/CSVExportButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface CSVExportButtonProps { 4 | data: string[][]; 5 | filename: string; 6 | } 7 | 8 | const CSVExportButton: React.FC = ({ data, filename }) => { 9 | const convertToCSV = (data: string[][]): string => { 10 | return data.map((row) => row.map((cell) => `"${cell}"`).join(',')).join('\n'); 11 | }; 12 | 13 | const handleExportClick = () => { 14 | const csvData = convertToCSV(data); 15 | const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' }); 16 | const url = URL.createObjectURL(blob); 17 | const link = document.createElement('a'); 18 | link.href = url; 19 | link.setAttribute('download', filename); 20 | document.body.appendChild(link); 21 | link.click(); 22 | document.body.removeChild(link); 23 | }; 24 | 25 | return ( 26 | 29 | ); 30 | }; 31 | 32 | export default CSVExportButton; 33 | -------------------------------------------------------------------------------- /src/ui/NTTBenchmark.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React, { useEffect, useState } from 'react'; 4 | import CSVExportButton from './CSVExportButton'; 5 | import { random_polynomial } from '../utils/aleoWasmFunctions'; 6 | import { bigIntsToU32Array, u32ArrayToBigInts } from '../gpu/utils'; 7 | import { ntt_multipass } from '../gpu/entries/ntt/nttMultipassEntry'; 8 | import { U256WGSL } from '../gpu/wgsl/U256'; 9 | import { FieldModulusWGSL } from '../gpu/wgsl/FieldModulus'; 10 | import { prune } from '../gpu/prune'; 11 | 12 | function bit_reverse(a: bigint[]): bigint[] { 13 | const n = a.length; 14 | const logN = Math.log2(n); 15 | 16 | const reverseBits = (num: number, bits: number): number => { 17 | let reversed = 0; 18 | for (let i = 0; i < bits; i++) { 19 | reversed = (reversed << 1) | (num & 1); 20 | num >>= 1; 21 | } 22 | return reversed; 23 | }; 24 | 25 | for (let i = 0; i < n; i++) { 26 | const rev = reverseBits(i, logN); 27 | if (i < rev) { 28 | [a[i], a[rev]] = [a[rev], a[i]]; 29 | } 30 | } 31 | return a; 32 | } 33 | 34 | interface NTTBenchmarkProps { 35 | name: string; 36 | fieldParamsWGSL: string; 37 | wasmNTT: (polynomial_coeffs: string[]) => Promise; 38 | rootsOfUnity: { [index: number]: bigint; }; 39 | fieldModulus: bigint; 40 | } 41 | 42 | export const NTTBenchmark: React.FC = ({ 43 | name, 44 | fieldParamsWGSL, 45 | wasmNTT, 46 | rootsOfUnity, 47 | fieldModulus 48 | }) => { 49 | const initialDefaultInputSize = 18; 50 | const [inputSize, setInputSize] = useState(initialDefaultInputSize); 51 | const [numEvaluations, setNumEvaluations] = useState(10); 52 | const [gpuTime, setGpuTime] = useState(0); 53 | const [wasmTime, setWasmTime] = useState(0); 54 | const [gpuRunning, setGpuRunning] = useState(false); 55 | const [wasmRunning, setWasmRunning] = useState(false); 56 | const initialResults: string[] = []; 57 | const [gpuResults, setGpuResults] = useState(initialResults); 58 | const [wasmResults, setWasmResults] = useState(initialResults); 59 | const [inputs, setInputs] = useState([]); 60 | const [comparison, setComparison] = useState('Run both GPU and WASM to compare results'); 61 | const [benchmarkResults, setBenchmarkResults] = useState([["InputSize", "GPUorWASM", "Time"]]); 62 | const [WnModules, setWnModules] = useState(''); 63 | const [ButterflyModules, setButterflyModules] = useState(''); 64 | 65 | const polynomialGenerator = async (inputSize: number): Promise => { 66 | const polynomial = await random_polynomial(inputSize); 67 | return polynomial; 68 | } 69 | 70 | useEffect(() => { 71 | polynomialGenerator(inputSize).then((polynomial) => { 72 | setInputs([polynomial]); 73 | }); 74 | const BaseModules = [U256WGSL, fieldParamsWGSL, FieldModulusWGSL]; 75 | setWnModules(prune(BaseModules.join("\n"), ['gen_field_pow'])); 76 | setButterflyModules(prune(BaseModules.join("\n"), ['gen_field_add', 'gen_field_sub', 'gen_field_multiply'])); 77 | }, []); 78 | 79 | useEffect(() => { 80 | polynomialGenerator(inputSize).then((polynomial) => { 81 | setInputs([polynomial]); 82 | }); 83 | }, [inputSize]); 84 | 85 | useEffect(() => { 86 | if (gpuResults.length === 0 && wasmResults.length === 0) { 87 | setComparison('Run benchmarks to compare results.') 88 | } else if (gpuResults.length === 0) { 89 | setComparison('🎮 Run GPU') 90 | } else if (wasmResults.length === 0) { 91 | setComparison('👾 Run WASM') 92 | } else if (gpuResults.length !== wasmResults.length) { 93 | setComparison('⛔️ Different length results'); 94 | } else { 95 | let gpuResultsDiffIndex = -1; 96 | for (let i = 0; i < gpuResults.length; i++) { 97 | if (gpuResults[i] !== wasmResults[i]) { 98 | gpuResultsDiffIndex = i; 99 | break; 100 | } 101 | } 102 | if (gpuResultsDiffIndex !== -1) { 103 | setComparison(`❌ different at index ${gpuResultsDiffIndex}`); 104 | } else { 105 | setComparison('✅'); 106 | } 107 | } 108 | }, [gpuResults, wasmResults]); 109 | 110 | const runWasm = async () => { 111 | const wasmInputs = inputs; 112 | setWasmRunning(true); 113 | const wasmStart = performance.now(); 114 | 115 | const results: string[] = await wasmNTT(wasmInputs[0]); 116 | const wasmEnd = performance.now(); 117 | const wasmPerformance = wasmEnd - wasmStart; 118 | setWasmTime(wasmPerformance); 119 | const comparableWasmResults = results; 120 | setWasmResults(comparableWasmResults); 121 | const benchMarkResult = [inputSize, "WASM", wasmPerformance]; 122 | setBenchmarkResults([...benchmarkResults, benchMarkResult]); 123 | setWasmRunning(false); 124 | }; 125 | 126 | const runGpu = async () => { 127 | const tempInputs = inputs[0].map((input) => BigInt(input)); 128 | 129 | setGpuRunning(true); 130 | 131 | const gpuStart = performance.now(); 132 | let result: Uint32Array | undefined; 133 | for (let i = 0; i < numEvaluations; i++) { 134 | const revInputs = bit_reverse(tempInputs); 135 | const tmpResult = await ntt_multipass( 136 | { u32Inputs: bigIntsToU32Array(revInputs), individualInputSize: 8 }, 137 | rootsOfUnity, 138 | fieldModulus, 139 | WnModules, 140 | ButterflyModules 141 | ); 142 | if (i === 0) { 143 | result = tmpResult; 144 | } 145 | } 146 | const gpuEnd = performance.now(); 147 | const gpuPerformance = gpuEnd - gpuStart; 148 | 149 | setGpuTime(gpuPerformance); 150 | 151 | const bigIntResult = u32ArrayToBigInts(result || new Uint32Array(0)); 152 | const results = bigIntResult.map(r => r.toString()); 153 | 154 | const benchMarkResult = [inputSize, "GPU", gpuPerformance]; 155 | setBenchmarkResults([...benchmarkResults, benchMarkResult]); 156 | 157 | setGpuResults(results); 158 | setGpuRunning(false); 159 | }; 160 | 161 | const spin = () =>
; 162 | 163 | return ( 164 |
165 |
{name}
166 |
Input Size (2^):
167 | setInputSize(parseInt(e.target.value))} 172 | /> 173 |
Evaluations
174 | setNumEvaluations(parseInt(e.target.value))} 179 | /> 180 | 183 |
{gpuTime > 0 ? gpuTime : 'GPU Time: 0ms'}
184 | 187 |
{wasmTime > 0 ? wasmTime : 'WASM Time: 0ms'}
188 |
{comparison}
189 | 190 |
191 | ); 192 | }; -------------------------------------------------------------------------------- /src/ui/PippengerBenchmark.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React, { useEffect, useState } from 'react'; 4 | import { u32ArrayToBigInts } from '../gpu/utils'; 5 | import CSVExportButton from './CSVExportButton'; 6 | import { ExtPointType } from '@noble/curves/abstract/edwards'; 7 | import { FieldMath } from '../utils/BLS12_377FieldMath'; 8 | 9 | interface pippengerBenchmarkProps { 10 | name: string; 11 | inputsGenerator: (inputSize: number) => Promise; 12 | gpuFunc: (points: ExtPointType[], scalars: number[], fieldMath: FieldMath) => Promise; 13 | gpuInputConverter: (scalars: bigint[]) => [ExtPointType[], number[], FieldMath]; 14 | gpuResultConverter?: (results: bigint[]) => string[]; 15 | wasmFunc: (inputs: any[][]) => Promise; 16 | wasmInputConverter: (inputs: any[][]) => any[][]; 17 | wasmResultConverter: (results: string[]) => string[]; 18 | } 19 | 20 | export const PippengerBenchmark: React.FC = ( 21 | {name, inputsGenerator, gpuFunc, gpuInputConverter, gpuResultConverter, wasmFunc, wasmInputConverter, wasmResultConverter} 22 | ) => { 23 | const initialDefaultInputSize = 1_000; 24 | const [inputSize, setInputSize] = useState(initialDefaultInputSize); 25 | const [gpuTime, setGpuTime] = useState(0); 26 | const [wasmTime, setWasmTime] = useState(0); 27 | const [gpuRunning, setGpuRunning] = useState(false); 28 | const [wasmRunning, setWasmRunning] = useState(false); 29 | const initialResults: string[] = []; 30 | const [gpuResults, setGpuResults] = useState(initialResults); 31 | const [wasmResults, setWasmResults] = useState(initialResults); 32 | const [inputs, setInputs] = useState([]); 33 | const [comparison, setComparison] = useState('Run both GPU and WASM to compare results'); 34 | const [benchmarkResults, setBenchmarkResults] = useState([["InputSize", "GPUorWASM", "Time"]]); 35 | 36 | useEffect(() => { 37 | const fetchInputs = async () => { 38 | const generatedInputs = await inputsGenerator(inputSize); 39 | setInputs(generatedInputs); 40 | }; 41 | fetchInputs(); 42 | }, []); 43 | 44 | useEffect(() => { 45 | const setNewInputs = async () => { 46 | const newInputs = await inputsGenerator(inputSize); 47 | setInputs(newInputs); 48 | }; 49 | setNewInputs(); 50 | }, [inputSize]); 51 | 52 | useEffect(() => { 53 | if (gpuResults.length === 0 && wasmResults.length === 0) { 54 | setComparison('Run benchmarks to compare results.') 55 | } else if (gpuResults.length === 0) { 56 | setComparison('🎮 Run GPU') 57 | } else if (wasmResults.length === 0) { 58 | setComparison('👾 Run WASM') 59 | } else if (gpuResults.length !== wasmResults.length) { 60 | setComparison('⛔️ Different length results'); 61 | } else { 62 | let gpuResultsDiffIndex = -1; 63 | for (let i = 0; i < gpuResults.length; i++) { 64 | if (gpuResults[i] !== wasmResults[i]) { 65 | gpuResultsDiffIndex = i; 66 | break; 67 | } 68 | } 69 | if (gpuResultsDiffIndex !== -1) { 70 | setComparison(`❌ different at index ${gpuResultsDiffIndex}`); 71 | } else { 72 | setComparison('✅'); 73 | } 74 | } 75 | }, [gpuResults, wasmResults]); 76 | 77 | const runWasm = async () => { 78 | const wasmInputs = wasmInputConverter(inputs); 79 | setWasmRunning(true); 80 | const wasmStart = performance.now(); 81 | const results: string[] = await wasmFunc(wasmInputs); 82 | const wasmEnd = performance.now(); 83 | const wasmPerformance = wasmEnd - wasmStart; 84 | setWasmTime(wasmPerformance); 85 | const comparableWasmResults = wasmResultConverter(results); 86 | console.log(comparableWasmResults); 87 | setWasmResults(comparableWasmResults); 88 | const benchMarkResult = [inputSize, "WASM", wasmPerformance]; 89 | setBenchmarkResults([...benchmarkResults, benchMarkResult]); 90 | setWasmRunning(false); 91 | }; 92 | 93 | const runGpu = async () => { 94 | const gpuInputs = gpuInputConverter(inputs[1]); 95 | setGpuRunning(true); 96 | const gpuStart = performance.now(); 97 | const result = await gpuFunc(gpuInputs[0], gpuInputs[1], gpuInputs[2]); 98 | const gpuEnd = performance.now(); 99 | const gpuPerformance = gpuEnd - gpuStart; 100 | setGpuTime(gpuPerformance); 101 | const bigIntResult = u32ArrayToBigInts(result || new Uint32Array(0)); 102 | const results = gpuResultConverter ? gpuResultConverter(bigIntResult) : bigIntResult.map(r => r.toString()); 103 | console.log(results); 104 | setGpuResults(results); 105 | const benchMarkResult = [inputSize, "GPU", gpuPerformance]; 106 | setBenchmarkResults([...benchmarkResults, benchMarkResult]); 107 | setGpuRunning(false); 108 | }; 109 | 110 | const spin = () =>
; 111 | 112 | return ( 113 |
114 |
{name}
115 |
Input Size:
116 | setInputSize(parseInt(e.target.value))} 121 | /> 122 |
Batch Size:
123 | 128 | 129 | 132 |
{gpuTime > 0 ? gpuTime : 'GPU Time: 0ms'}
133 | 136 |
{wasmTime > 0 ? wasmTime : 'WASM Time: 0ms'}
137 |
{comparison}
138 | 139 |
140 | ); 141 | }; -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{html,js,jsx,ts,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "allowSyntheticDefaultImports": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react", 17 | "typeRoots": [ "./node_modules/@webgpu/types", "./node_modules/@types" ], 18 | "outDir": "dist" 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "dist", "**/barretenberg-wasm-loader"], 22 | "ts-node": { 23 | // Tell ts-node CLI to install the --loader automatically, explained below 24 | "esm": true, 25 | "compilerOptions": { 26 | "module": "CommonJS", 27 | "moduleResolution": "NodeNext", 28 | } 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /webpack.dev.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require("path"); 3 | const webpack = require("webpack"); 4 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 5 | const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); 6 | const ESLintPlugin = require("eslint-webpack-plugin"); 7 | 8 | const config = { 9 | mode: "development", 10 | output: { 11 | publicPath: "/", 12 | }, 13 | entry: "./src/index.tsx", 14 | experiments: { 15 | asyncWebAssembly: true 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.worker\.ts$/, 21 | loader: 'worker-loader', 22 | options: { inline: 'no-fallback' }, 23 | }, 24 | { 25 | test: /\.(ts|js)x?$/i, 26 | exclude: /node_modules/, 27 | use: { 28 | loader: "babel-loader", 29 | options: { 30 | presets: [ 31 | "@babel/preset-env", 32 | "@babel/preset-react", 33 | "@babel/preset-typescript", 34 | ], 35 | }, 36 | }, 37 | }, 38 | { 39 | test: /\.css$/i, 40 | exclude: /node_modules/, 41 | use: ['style-loader', 'css-loader', 'postcss-loader'], 42 | }, 43 | ], 44 | }, 45 | resolve: { 46 | extensions: [".tsx", ".ts", ".js"], 47 | fallback: { 48 | "crypto": require.resolve("crypto-browserify"), 49 | "stream": require.resolve("stream-browserify"), 50 | "buffer": require.resolve("buffer/"), 51 | "path": require.resolve("path-browserify") 52 | } 53 | }, 54 | plugins: [ 55 | new HtmlWebpackPlugin({ 56 | template: "src/index.html", 57 | }), 58 | new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'] }), 59 | new webpack.HotModuleReplacementPlugin(), 60 | new ForkTsCheckerWebpackPlugin({ 61 | async: false, 62 | }), 63 | new ESLintPlugin({ 64 | extensions: ["js", "jsx", "ts", "tsx"], 65 | }), 66 | ], 67 | devtool: "inline-source-map", 68 | devServer: { 69 | static: path.join(__dirname, "build"), 70 | historyApiFallback: true, 71 | port: 4000, 72 | open: true, 73 | hot: true, 74 | client: { 75 | overlay: false 76 | }, 77 | headers: { 78 | 'Cross-Origin-Opener-Policy': 'same-origin', 79 | 'Cross-Origin-Embedder-Policy': 'require-corp', 80 | } 81 | }, 82 | }; 83 | 84 | // export default config; 85 | 86 | module.exports = config; 87 | -------------------------------------------------------------------------------- /webpack.prod.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require("path"); 3 | const webpack = require("webpack"); 4 | const webpackServer = require('webpack-dev-server'); 5 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 6 | const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); 7 | const ESLintPlugin = require("eslint-webpack-plugin"); 8 | const cleanWebpackPlugin = require("clean-webpack-plugin"); 9 | 10 | const config = { 11 | mode: "production", 12 | entry: "./src/index.tsx", 13 | experiments: { 14 | asyncWebAssembly: true 15 | }, 16 | output: { 17 | path: path.resolve(__dirname, "build"), 18 | filename: "[name].[contenthash].js", 19 | publicPath: "", 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.(ts|js)x?$/i, 25 | exclude: /node_modules/, 26 | use: { 27 | loader: "babel-loader", 28 | options: { 29 | presets: [ 30 | "@babel/preset-env", 31 | "@babel/preset-react", 32 | "@babel/preset-typescript", 33 | ], 34 | }, 35 | }, 36 | }, 37 | ], 38 | }, 39 | resolve: { 40 | extensions: [".tsx", ".ts", ".js"], 41 | fallback: { 42 | "crypto": require.resolve("crypto-browserify"), 43 | "stream": require.resolve("stream-browserify"), 44 | "buffer": require.resolve("buffer/") 45 | } 46 | }, 47 | plugins: [ 48 | new HtmlWebpackPlugin({ 49 | template: "src/index.html", 50 | }), 51 | new ForkTsCheckerWebpackPlugin({ 52 | async: false, 53 | }), 54 | new ESLintPlugin({ 55 | extensions: ["js", "jsx", "ts", "tsx"], 56 | }), 57 | new cleanWebpackPlugin.CleanWebpackPlugin(), 58 | ], 59 | }; 60 | 61 | module.exports = config; 62 | --------------------------------------------------------------------------------