├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── typescript_react │ ├── .editorconfig │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── index.html │ ├── package.json │ ├── pnpm-lock.yaml │ ├── public │ │ ├── assets │ │ │ ├── blsjs.wasm │ │ │ └── clvm_wasm_bg.wasm │ │ └── robots.txt │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── index.css │ │ └── index.tsx │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts └── typescript_webpack │ ├── .editorconfig │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ ├── index.html │ ├── index.scss │ └── index.ts │ ├── tsconfig.json │ └── webpack.config.js ├── jest.config.js ├── package.json ├── pnpm-lock.yaml ├── pre_build.js ├── src ├── CLVMObject.ts ├── EvalError.ts ├── SExp.ts ├── __bls_signatures__ │ ├── index.ts │ ├── loader.d.ts │ └── loader.js ├── __clvm_wasm__.ts ├── __debug__.ts ├── __python_types__.ts ├── __type_compatibility__.ts ├── as_javascript.ts ├── casts.ts ├── core_ops.ts ├── costs.ts ├── index.ts ├── initialize.ts ├── more_ops.ts ├── op_utils.ts ├── operators.ts ├── run_program.ts └── serialize.ts ├── tests ├── _benchmark.js ├── _casts_test.ts ├── _type_compatibility_test.ts ├── as_javascript_test.ts ├── bls12_381_test.ts ├── operatordict_test.ts ├── operators_test.ts ├── original │ └── clvm_wasm_test.ts ├── run_program_test.ts ├── serialize_test.ts └── to_sexp_test.ts ├── tsconfig.json └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | trim_trailing_whitespace=false 5 | insert_final_newline=false 6 | indent_style=space 7 | indent_size=2 8 | 9 | [{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}] 10 | indent_style=space 11 | indent_size=2 12 | 13 | [{.eslintrc,.prettierrc,.babelrc,jest.config,.stylelintrc,*.json,*.jsb3,*.jsb2,*.bowerrc}] 14 | indent_style=space 15 | indent_size=2 16 | 17 | [*.svg] 18 | indent_style=space 19 | indent_size=2 20 | 21 | [*.css] 22 | indent_style=space 23 | indent_size=2 24 | 25 | [*.scss] 26 | indent_style=space 27 | indent_size=2 28 | 29 | [*.styl] 30 | indent_style=space 31 | indent_size=2 32 | 33 | [*.js] 34 | indent_style=space 35 | indent_size=2 36 | 37 | [{*.ats,*.ts}] 38 | indent_style=space 39 | indent_size=2 40 | ij_typescript_import_use_node_resolution = false 41 | 42 | [*.tsx] 43 | indent_style=space 44 | indent_size=2 45 | ij_typescript_import_use_node_resolution = false 46 | 47 | [{*.mjs,*.es6}] 48 | indent_style=space 49 | indent_size=2 50 | 51 | [*.js.flow] 52 | indent_style=space 53 | indent_size=2 54 | 55 | [*.jsx] 56 | indent_style=space 57 | indent_size=2 58 | 59 | [{jshint.json,*.jshintrc}] 60 | indent_style=space 61 | indent_size=2 62 | 63 | [{tsconfig.e2e.json,tsconfig.spec.json,tsconfig.app.json,tsconfig.json}] 64 | indent_style=space 65 | indent_size=2 66 | 67 | [*.js.map] 68 | indent_style=space 69 | indent_size=2 70 | 71 | [{.analysis_options,*.yml,*.yaml}] 72 | indent_style=space 73 | indent_size=2 74 | 75 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/**/__tests__/** 2 | node_modules 3 | .eslintrc.js 4 | webpack.*.js 5 | tests/ 6 | bin/ 7 | pre_build.js 8 | jest.config.js 9 | src/__bls_signatures__/loader.js 10 | src/__clvm_wasm__/loader.js 11 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "es6": true, 4 | "jest": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/eslint-recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:import/errors", 11 | "plugin:import/warnings", 12 | "plugin:import/typescript" 13 | ], 14 | "globals": { 15 | "Atomics": "readonly", 16 | "SharedArrayBuffer": "readonly" 17 | }, 18 | "parser": "@typescript-eslint/parser", 19 | "parserOptions": { 20 | "ecmaFeatures": { 21 | "jsx": true 22 | }, 23 | "project": "./tsconfig.json", 24 | "ecmaVersion": 2018, 25 | "sourceType": "module" 26 | }, 27 | "plugins": [ 28 | "@typescript-eslint", 29 | ], 30 | "settings": { 31 | }, 32 | "rules": { 33 | "@typescript-eslint/array-type": [ 34 | "error", 35 | { 36 | "default": "array-simple" 37 | } 38 | ], 39 | "@typescript-eslint/ban-types": [ 40 | "error", 41 | { 42 | "types": { 43 | "Object": { 44 | "message": "Avoid using the `Object` type. Did you mean `object`?" 45 | }, 46 | "Function": { 47 | "message": "Avoid using the `Function` type. Prefer a specific function type, like `() => void`." 48 | }, 49 | "Boolean": { 50 | "message": "Avoid using the `Boolean` type. Did you mean `boolean`?" 51 | }, 52 | "Number": { 53 | "message": "Avoid using the `Number` type. Did you mean `number`?" 54 | }, 55 | "String": { 56 | "message": "Avoid using the `String` type. Did you mean `string`?" 57 | }, 58 | "Symbol": { 59 | "message": "Avoid using the `Symbol` type. Did you mean `symbol`?" 60 | } 61 | } 62 | } 63 | ], 64 | "@typescript-eslint/consistent-type-assertions": "error", 65 | "@typescript-eslint/consistent-type-definitions": "off", 66 | "@typescript-eslint/dot-notation": "off", 67 | "@typescript-eslint/explicit-module-boundary-types": "off", 68 | "@typescript-eslint/indent": [ 69 | "error", 70 | 2, 71 | { 72 | "SwitchCase": 1, 73 | "ObjectExpression": "first", 74 | "FunctionDeclaration": { 75 | "parameters": "first" 76 | }, 77 | "FunctionExpression": { 78 | "parameters": "first" 79 | }, 80 | "ignoredNodes": ["TSTypeParameterInstantiation"] 81 | } 82 | ], 83 | "@typescript-eslint/interface-name-prefix": "off", 84 | "@typescript-eslint/member-ordering": "off", 85 | "@typescript-eslint/naming-convention": [ 86 | "off", 87 | { 88 | "selector": "variable", 89 | "format": [ 90 | "camelCase", 91 | "PascalCase", 92 | "UPPER_CASE" 93 | ], 94 | "leadingUnderscore": "allow" 95 | }, 96 | { 97 | "selector": "typeLike", 98 | "format": ["PascalCase"] 99 | }, 100 | { 101 | "selector": "memberLike", 102 | "modifiers": ["private"], 103 | "format": ["camelCase"], 104 | "leadingUnderscore": "require" 105 | } 106 | ], 107 | "@typescript-eslint/no-empty-function": "error", 108 | "@typescript-eslint/no-empty-interface": "off", 109 | "@typescript-eslint/no-explicit-any": "off", 110 | "@typescript-eslint/no-inferrable-types": "off", 111 | "@typescript-eslint/no-misused-new": "error", 112 | "@typescript-eslint/no-namespace": "error", 113 | "@typescript-eslint/no-parameter-properties": "off", 114 | "@typescript-eslint/no-this-alias": [ 115 | "off", 116 | { 117 | "allowDestructuring": true 118 | } 119 | ], 120 | "@typescript-eslint/no-unused-expressions": "error", 121 | "@typescript-eslint/no-unused-vars": [ 122 | "error", 123 | { 124 | "args": "none" 125 | } 126 | ], 127 | "@typescript-eslint/no-use-before-define": "off", 128 | "@typescript-eslint/no-var-requires": "error", 129 | "@typescript-eslint/prefer-for-of": "off", 130 | "@typescript-eslint/prefer-function-type": "error", 131 | "@typescript-eslint/prefer-namespace-keyword": "error", 132 | "@typescript-eslint/triple-slash-reference": [ 133 | "error", 134 | { 135 | "path": "always", 136 | "types": "prefer-import", 137 | "lib": "always" 138 | } 139 | ], 140 | "@typescript-eslint/unified-signatures": "error", 141 | "arrow-parens": [ 142 | "off", 143 | "always" 144 | ], 145 | "brace-style": [ 146 | "off", 147 | "off" 148 | ], 149 | "complexity": "off", 150 | "constructor-super": "error", 151 | "eqeqeq": [ 152 | "error", 153 | "smart" 154 | ], 155 | "guard-for-in": "error", 156 | "id-match": "error", 157 | "import/no-extraneous-dependencies": "error", 158 | "import/no-internal-modules": [ 159 | "off", 160 | { 161 | "allow": [ 162 | "excluded-module1/*", 163 | "excluded-module2/*" 164 | ] 165 | } 166 | ], 167 | "import/order": "off", 168 | "max-classes-per-file": "off", 169 | "max-len": [ 170 | "error", 171 | { 172 | "ignorePattern": "//|^import |^export ", 173 | "code": 140 174 | } 175 | ], 176 | "new-parens": "error", 177 | "no-bitwise": "off", 178 | "no-caller": "error", 179 | "no-cond-assign": "error", 180 | "no-console": "off", 181 | "no-debugger": "error", 182 | "no-duplicate-case": "error", 183 | "no-duplicate-imports": "error", 184 | "no-empty": "error", 185 | "no-eval": "error", 186 | "no-extra-bind": "error", 187 | "no-fallthrough": "off", 188 | "no-invalid-this": "off", 189 | "no-multiple-empty-lines": "off", 190 | "no-new-func": "error", 191 | "no-new-wrappers": "error", 192 | "no-redeclare": "error", 193 | "no-return-await": "error", 194 | "no-sequences": "error", 195 | "no-shadow": [ 196 | "off", 197 | { 198 | "hoist": "all" 199 | } 200 | ], 201 | "no-sparse-arrays": "error", 202 | "no-template-curly-in-string": "error", 203 | "no-throw-literal": "error", 204 | "no-trailing-spaces": [ 205 | "error", 206 | { 207 | "ignoreComments": true, 208 | "skipBlankLines": true 209 | } 210 | ], 211 | "no-undef-init": "error", 212 | "no-underscore-dangle": "off", 213 | "no-unsafe-finally": "error", 214 | "no-unused-labels": "error", 215 | "no-var": "error", 216 | "object-shorthand": "off", 217 | "one-var": [ 218 | "error", 219 | "never" 220 | ], 221 | "prefer-const": "error", 222 | "prefer-object-spread": "error", 223 | "quotes": ["error", "double"], 224 | "quote-props": [ 225 | "off", 226 | "as-needed" 227 | ], 228 | "radix": "error", 229 | "space-in-parens": [ 230 | "error", 231 | "never" 232 | ], 233 | "spaced-comment": [ 234 | "error", 235 | "always", 236 | { 237 | "markers": [ 238 | "/" 239 | ] 240 | } 241 | ], 242 | "use-isnan": "error", 243 | "valid-typeof": "error" 244 | } 245 | }; -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | - '!main' 8 | pull_request: 9 | branches: 10 | - '*' 11 | - '!main' 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Setup Node 18 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: '18.x' 23 | 24 | - name: Get yarn version 25 | run: echo "yarn_version=$(yarn -v)" >> $GITHUB_ENV 26 | 27 | - name: Get yarn(v1) cache directory path 28 | if: startsWith(env.yarn_version, '1') 29 | run: echo "cache_dir=$(yarn cache dir)" >> $GITHUB_ENV 30 | 31 | - name: Get yarn(v2) cache directory path 32 | if: startsWith(env.yarn_version, '2') 33 | run: echo "cache_dir=$(yarn config get cacheFolder)" 34 | 35 | - name: Cache Node.js modules 36 | id: yarn-cache 37 | uses: actions/cache@v4 38 | with: 39 | path: ${{ env.cache_dir }} 40 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 41 | restore-keys: | 42 | ${{ runner.os }}-yarn- 43 | 44 | - name: Install dependencies 45 | run: yarn --frozen-lockfile 46 | 47 | - run: yarn build 48 | - run: yarn test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/**/usage.statistics.xml 10 | .idea/**/dictionaries 11 | .idea/**/shelf 12 | 13 | # Generated files 14 | .idea/**/contentModel.xml 15 | 16 | # Sensitive or high-churn files 17 | .idea/**/dataSources/ 18 | .idea/**/dataSources.ids 19 | .idea/**/dataSources.local.xml 20 | .idea/**/sqlDataSources.xml 21 | .idea/**/dynamic.xml 22 | .idea/**/uiDesigner.xml 23 | .idea/**/dbnavigator.xml 24 | 25 | # Gradle 26 | .idea/**/gradle.xml 27 | .idea/**/libraries 28 | 29 | # Gradle and Maven with auto-import 30 | # When using Gradle or Maven with auto-import, you should exclude module files, 31 | # since they will be recreated, and may cause churn. Uncomment if using 32 | # auto-import. 33 | # .idea/artifacts 34 | # .idea/compiler.xml 35 | # .idea/jarRepositories.xml 36 | # .idea/modules.xml 37 | # .idea/*.iml 38 | # .idea/modules 39 | # *.iml 40 | # *.ipr 41 | 42 | # CMake 43 | cmake-build-*/ 44 | 45 | # Mongo Explorer plugin 46 | .idea/**/mongoSettings.xml 47 | 48 | # File-based project format 49 | *.iws 50 | 51 | # IntelliJ 52 | out/ 53 | 54 | # mpeltonen/sbt-idea plugin 55 | .idea_modules/ 56 | 57 | # JIRA plugin 58 | atlassian-ide-plugin.xml 59 | 60 | # Cursive Clojure plugin 61 | .idea/replstate.xml 62 | 63 | # Crashlytics plugin (for Android Studio and IntelliJ) 64 | com_crashlytics_export_strings.xml 65 | crashlytics.properties 66 | crashlytics-build.properties 67 | fabric.properties 68 | 69 | # Editor-based Rest Client 70 | .idea/httpRequests 71 | 72 | # Android studio 3.1+ serialized cache file 73 | .idea/caches/build_file_checksums.ser 74 | 75 | ### macOS template 76 | # General 77 | .DS_Store 78 | .AppleDouble 79 | .LSOverride 80 | 81 | # Icon must end with two \r 82 | Icon 83 | 84 | # Thumbnails 85 | ._* 86 | 87 | # Files that might appear in the root of a volume 88 | .DocumentRevisions-V100 89 | .fseventsd 90 | .Spotlight-V100 91 | .TemporaryItems 92 | .Trashes 93 | .VolumeIcon.icns 94 | .com.apple.timemachine.donotpresent 95 | 96 | # Directories potentially created on remote AFP share 97 | .AppleDB 98 | .AppleDesktop 99 | Network Trash Folder 100 | Temporary Items 101 | .apdisk 102 | 103 | ### Node template 104 | # Logs 105 | logs 106 | *.log 107 | npm-debug.log* 108 | yarn-debug.log* 109 | yarn-error.log* 110 | lerna-debug.log* 111 | 112 | # Diagnostic reports (https://nodejs.org/api/report.html) 113 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 114 | 115 | # Runtime data 116 | pids 117 | *.pid 118 | *.seed 119 | *.pid.lock 120 | 121 | # Directory for instrumented libs generated by jscoverage/JSCover 122 | lib-cov 123 | 124 | # Coverage directory used by tools like istanbul 125 | #coverage 126 | #*.lcov 127 | 128 | # nyc test coverage 129 | .nyc_output 130 | 131 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 132 | .grunt 133 | 134 | # Bower dependency directory (https://bower.io/) 135 | bower_components 136 | 137 | # node-waf configuration 138 | .lock-wscript 139 | 140 | # Compiled binary addons (https://nodejs.org/api/addons.html) 141 | build/Release 142 | 143 | # Dependency directories 144 | node_modules/ 145 | jspm_packages/ 146 | 147 | # Snowpack dependency directory (https://snowpack.dev/) 148 | web_modules/ 149 | 150 | # TypeScript cache 151 | *.tsbuildinfo 152 | 153 | # Optional npm cache directory 154 | .npm 155 | 156 | # Optional eslint cache 157 | .eslintcache 158 | 159 | # Microbundle cache 160 | .rpt2_cache/ 161 | .rts2_cache_cjs/ 162 | .rts2_cache_es/ 163 | .rts2_cache_umd/ 164 | 165 | # Optional REPL history 166 | .node_repl_history 167 | 168 | # Output of 'npm pack' 169 | *.tgz 170 | 171 | # Yarn Integrity file 172 | .yarn-integrity 173 | 174 | # dotenv environment variables file 175 | .env 176 | .env.test 177 | 178 | # parcel-bundler cache (https://parceljs.org/) 179 | .cache 180 | .parcel-cache 181 | 182 | # Next.js build output 183 | .next 184 | 185 | # Nuxt.js build / generate output 186 | .nuxt 187 | # dist 188 | 189 | # Gatsby files 190 | .cache/ 191 | # Comment in the public line in if your project uses Gatsby and not Next.js 192 | # https://nextjs.org/blog/next-9-1#public-directory-support 193 | # public 194 | 195 | # vuepress build output 196 | .vuepress/dist 197 | 198 | # Serverless directories 199 | .serverless/ 200 | 201 | # FuseBox cache 202 | .fusebox/ 203 | 204 | # DynamoDB Local files 205 | .dynamodb/ 206 | 207 | # TernJS port file 208 | .tern-port 209 | 210 | # Stores VSCode versions used for testing VSCode extensions 211 | .vscode-test 212 | 213 | # yarn v2 214 | 215 | .yarn/cache 216 | .yarn/unplugged 217 | .yarn/build-state.yml 218 | .pnp.* 219 | 220 | ### Windows template 221 | # Windows thumbnail cache files 222 | Thumbs.db 223 | Thumbs.db:encryptable 224 | ehthumbs.db 225 | ehthumbs_vista.db 226 | 227 | # Dump file 228 | *.stackdump 229 | 230 | # Folder config file 231 | [Dd]esktop.ini 232 | 233 | # Recycle Bin used on file shares 234 | $RECYCLE.BIN/ 235 | 236 | # Windows Installer files 237 | *.cab 238 | *.msi 239 | *.msix 240 | *.msm 241 | *.msp 242 | 243 | # Windows shortcuts 244 | *.lnk 245 | 246 | ### VisualStudioCode template 247 | .vscode/* 248 | !.vscode/settings.json 249 | !.vscode/tasks.json 250 | !.vscode/launch.json 251 | !.vscode/extensions.json 252 | *.code-workspace 253 | 254 | # Local History for Visual Studio Code 255 | .history/ 256 | 257 | .idea/ 258 | .npmrc 259 | build/ 260 | playground/ 261 | .dist/ 262 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2019 Admin ChiaMineJP 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clvm 2 | Javascript implementation of CLVM (Chia Lisp VM) 3 | 4 | ## Install 5 | ```shell 6 | npm install clvm 7 | # or 8 | yarn add clvm 9 | ``` 10 | 11 | ## Test 12 | `clvm-js` passes all the test equivalent to Python's original `clvm`. 13 | You can compare test files for [Javascript](./tests) and [Python](https://github.com/Chia-Network/clvm/tree/main/tests) 14 | To run the test, execute the following command. 15 | ```shell 16 | git clone https://github.com/Chia-Mine/clvm-js 17 | cd clvm-js 18 | 19 | npm install 20 | npm run test 21 | # or 22 | yarn 23 | yarn test 24 | ``` 25 | If you find something not compatible with Python's clvm, please report it to GitHub issues. 26 | 27 | ## Compatibility 28 | This code is compatible with: 29 | - [`960f8d139940fa0814d3fac44da9a2975642f5d3`](https://github.com/Chia-Network/clvm/tree/960f8d139940fa0814d3fac44da9a2975642f5d3) of [clvm](https://github.com/Chia-Network/clvm) 30 | - [Diff to the latest clvm](https://github.com/Chia-Network/clvm/compare/960f8d139940fa0814d3fac44da9a2975642f5d3...main) 31 | - [`34f504bd0ef2cd3a219fea8ce6b15ff7684687fd`](https://github.com/Chia-Network/bls-signatures/tree/34f504bd0ef2cd3a219fea8ce6b15ff7684687fd) of [bls-signatures](https://github.com/Chia-Network/bls-signatures) 32 | - [Diff to the latest bls-signatures](https://github.com/Chia-Network/bls-signatures/compare/34f504bd0ef2cd3a219fea8ce6b15ff7684687fd...main) 33 | 34 | ## Example 35 | ```javascript 36 | // in nodejs context 37 | async function main(){ 38 | const clvm = require("clvm"); 39 | 40 | // `await clvm.initializeClvmWasm()` is now always necessary. 41 | // This loads clvm_wasm_bg.wasm. 42 | // If you have a strong reason to use features of bls-signatures, 43 | // you need to do `await clvm.initialize()` instead. 44 | // `clvm.initialize()` loads both blsjs.wasm and clvm_wasm_bg.wasm. 45 | await clvm.initializeClvmWasm(); 46 | 47 | const {SExp, KEYWORD_TO_ATOM, h, t, run_chia_program, Flag} = clvm; 48 | const plus = h(KEYWORD_TO_ATOM["+"]); // byte representation of '+' operator 49 | const q = h(KEYWORD_TO_ATOM["q"]); // byte representation of 'q' operator 50 | const program = SExp.to([plus, 1, t(q, 175)]).as_bin().raw(); // (+ . (1 . (q . 175))) 51 | const env = SExp.to(25).as_bin().raw(); 52 | const max_cost = BigInt(10000000); 53 | const [cost, lazyNode] = run_chia_program(program, env, max_cost, Flag.allow_backrefs()); 54 | const result = new SExp(lazyNode); 55 | let isEqual = result.equal_to(SExp.to(25 + 175)); 56 | console.log(`isEqual: ${isEqual}`); // 'isEqual: true' 57 | isEqual = result.as_int() === (25 + 175); 58 | console.log(`isEqual: ${isEqual}`); // 'isEqual: true' 59 | } 60 | 61 | main().catch(e => console.error(e)); 62 | ``` 63 | 64 | ## More example with clvm_wasm 65 | See this [test case for clvm_wasm](https://github.com/Chia-Mine/clvm-js/blob/v3.0.0/tests/original/clvm_wasm_test.ts) 66 | 67 | ## Use in browser 68 | - [Sample code - Use clvm-js with TypeScript, Webpack](https://github.com/Chia-Mine/clvm-js/blob/v3.0.0/example/typescript_webpack) 69 | - [Sample code - Use clvm-js with TypeScript, React, Vite](https://github.com/Chia-Mine/clvm-js/blob/v3.0.0/example/typescript_react) 70 | 71 | If you'd like to run some javascript code which depends on `clvm` on browser, 72 | you need to put `clvm_wasm_bg.wasm` and optionally `blsjs.wasm` to the same directory as the code who loads `clvm`. 73 | Because most of BLS operations are now performed inside `clvm_wasm_bg.wasm`, in most cases you don't need `blsjs.wasm`. 74 | 75 |
 76 | ├── ...
 77 | ├── main.js      # js file which clvm is compiled into
 78 | ├── clvm_wasm_bg.wasm   # copy it from npm_modules/clvm/browser/clvm_wasm_bg.wasm
 79 | └── (Optional) blsjs.wasm   # copy it from npm_modules/clvm/browser/blsjs.wasm
 80 | 
81 | 82 | If you use [React](https://reactjs.org/) with [CRA(create-react-app)](https://github.com/facebook/create-react-app), copy `blsjs.wasm` and `clvm_wasm_bg.wasm` into `/public/static/js/` folder. It automatically copies wasm file next to main js file. 83 | 84 | If you use [React](https://reactjs.org/) with [vite](https://vitejs.dev/), 85 | copy `blsjs.wasm` and `clvm_wasm_bg.wasm` into `/public/assets/` folder. 86 | 87 | **IMPORTANT NOTE** 88 | When your code is loaded as a module, such as with ` 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/typescript_react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-clvm-with-react", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "dependencies": { 7 | "clvm": "^3.0.0", 8 | "react": "^18.3.1", 9 | "react-dom": "^18.3.1" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "^20.12.11", 13 | "@types/react": "^18.3.2", 14 | "@types/react-dom": "^18.3.0", 15 | "@typescript-eslint/eslint-plugin": "^7.8.0", 16 | "@typescript-eslint/parser": "^7.8.0", 17 | "@vitejs/plugin-react-swc": "^3.6.0", 18 | "eslint-import-resolver-typescript": "^3.6.1", 19 | "eslint-plugin-import": "^2.29.1", 20 | "eslint-plugin-react": "^7.34.1", 21 | "eslint-plugin-react-hooks": "^4.6.2", 22 | "eslint-plugin-react-refresh": "^0.4.6", 23 | "typescript": "^5.4.5", 24 | "vite": "^5.2.11" 25 | }, 26 | "scripts": { 27 | "start": "vite", 28 | "build": "tsc && vite build", 29 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/typescript_react/public/assets/blsjs.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chia-Mine/clvm-js/3fb33f559b88c88fb86f3706d7affd483a472c62/example/typescript_react/public/assets/blsjs.wasm -------------------------------------------------------------------------------- /example/typescript_react/public/assets/clvm_wasm_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chia-Mine/clvm-js/3fb33f559b88c88fb86f3706d7affd483a472c62/example/typescript_react/public/assets/clvm_wasm_bg.wasm -------------------------------------------------------------------------------- /example/typescript_react/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /example/typescript_react/src/App.css: -------------------------------------------------------------------------------- 1 | .app-root { 2 | width: 800px; 3 | margin: auto; 4 | } 5 | 6 | * { 7 | box-sizing: border-box; 8 | } 9 | 10 | .input label { 11 | display: block; 12 | margin-bottom: 4px; 13 | } 14 | 15 | .input input { 16 | width: 100%; 17 | } 18 | 19 | .input + .input { 20 | margin-top: 16px; 21 | } 22 | 23 | .buttons { 24 | margin-top: 16px; 25 | position: relative; 26 | } 27 | 28 | .buttons button { 29 | padding: 4px 8px; 30 | } 31 | 32 | .buttons button + button { 33 | margin-left: 16px; 34 | } 35 | 36 | .buttons button:last-child { 37 | position: absolute; 38 | right: 0; 39 | } 40 | 41 | .output-container { 42 | margin-top: 16px; 43 | } 44 | 45 | #output { 46 | border: 1px solid #ccc; 47 | padding: 8px; 48 | white-space: pre-wrap; 49 | } 50 | -------------------------------------------------------------------------------- /example/typescript_react/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | h, 4 | SExp, 5 | CLVMType, 6 | run_chia_program, 7 | Flag, 8 | } from 'clvm/browser'; 9 | import './App.css'; 10 | 11 | function App() { 12 | const [output, setOutput] = React.useState(''); 13 | 14 | /* 15 | This comes from a block of height 600043 in the testnet11. 16 | You can get the block data by: 17 | chia rpc full_node get_blocks '{"start": 600043, "end": 600044}' 18 | on testnet11. 19 | The transactions_generator in the block was serialized with backref enabled. 20 | */ 21 | // eslint-disable-next-line max-len 22 | const [prg, setPrg] = React.useState('0xff01ff01ffffffa09dc16b766b557d0d8d94fe1ee636245b4417a46cd53bd4e70c26a62dc698d406ffff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04fffe84016b6b7fff80808080fffe820db78080fe81ffffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101fe6f80ff0180fe3e80ffff04ffff01b0a282b4b0117a8d04906835fa4fa0e13d3fbd1dd61899ebdf5157977611d1bae52f2ea97cbc3916466b1c9176d30a9030fe3f80ff85727e956731ffff80ffff01ffff33ffa05597ef68eaf171a6303995ecbb14fdbf2c24300b625bfbc886ea68270424661dff5880ffff33ffa07a7a9cb053b9e7086ddbb789a4a1abc646a06627d372eca59368bf90c15028bfff85727b9a765980ffff34ff8402faf08080ffff3cffa0d71f4c45af09583209498dbb9974bbda21b859fac0bf3348337ed33a2ba5c3838080ff808080808080'); 23 | const [env, setEnv] = React.useState('0x80'); 24 | 25 | const onInputChange = React.useCallback((e: React.ChangeEvent) => { 26 | const inputEl = e.currentTarget as HTMLInputElement; 27 | const setValue = inputEl.id === 'prg' ? setPrg : setEnv; 28 | setValue(inputEl.value); 29 | }, []); 30 | 31 | const onButtonClicked = React.useCallback((_: React.MouseEvent) => { 32 | setOutput(''); 33 | 34 | if (!prg || !env) { 35 | setOutput('program or env is not specified'); 36 | return; 37 | } 38 | 39 | try { 40 | const prgU8 = h(prg || '').raw(); 41 | const envU8 = h(env || '').raw(); 42 | 43 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 44 | // @ts-expect-error 45 | window.aaa = Flag; 46 | 47 | const result = run_chia_program(prgU8, envU8, 1000000000n, Flag.allow_backrefs()); 48 | const [cost, node] = result; 49 | const sexp = new SExp(node as CLVMType); 50 | const sexpStr = JSON.stringify(sexp, null, 2); 51 | setOutput(`Cost: ${cost.toString()}\r\nSExp: ${sexpStr}`); 52 | } catch (e: any) { 53 | console.error(e); 54 | if (e && typeof e === 'object' && 'name' in e && 'message' in e) { 55 | const err = e as Error; 56 | setOutput(`${err.name}: ${err.message}`); 57 | } else { 58 | setOutput(typeof e === 'string' ? e : JSON.stringify(e)); 59 | } 60 | } 61 | }, [env, prg]); 62 | 63 | const onClearButtonClicked = React.useCallback(() => { 64 | setOutput(''); 65 | }, []); 66 | 67 | return ( 68 |
69 |

Chia-Mine/clvm-js

70 |
71 | 72 | 73 |
74 |
75 | 76 | 77 |
78 |
79 | 82 | 85 |
86 |
87 | 88 |
{output}
89 |
90 |
91 | ); 92 | } 93 | 94 | export const PureApp = React.memo(App); 95 | -------------------------------------------------------------------------------- /example/typescript_react/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /example/typescript_react/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import {initializeClvmWasm} from 'clvm/browser'; 4 | import {PureApp} from './App.tsx'; 5 | 6 | async function main() { 7 | /** 8 | * Note: 9 | * `clvm_wasm_bg.wasm` is assumed to be put in the folder where the main js file resides. 10 | * For example, if the url of the main (compiled) code is https://example.com/test-app/main.js, 11 | * the func `initializeClvmWasm()` usually tries to load wasm files from 12 | * - https://example.com/test-app/clvm_wasm_bg.wasm 13 | * 14 | * But how the function knows the script dir? 15 | * The answer: The script dir url is stored in `document.currentScript` if not running on module context. 16 | * Since `document.currentScript` is `null` with module context, you need to get script dir by yourself. 17 | * If you don't specify the path to wasm, `initializeClvmWasm()` tries to load from the url 18 | * - https://example.com/clvm_wasm_bg.wasm 19 | * 20 | * As you may notice, this example uses TypeScript, React with Vite and the code is loaded as an ESModule, 21 | * where the code is run by . 22 | * With ESModule, you need to specify the path of wasm files like below. 23 | */ 24 | const scriptDir = import.meta.url.replace(/\/[^/]+$/, ''); 25 | const pathToWasm = `${scriptDir}/clvm_wasm_bg.wasm`; 26 | await initializeClvmWasm({pathToWasm}); 27 | 28 | /** 29 | * Note for blsjs.wasm. 30 | * As of 14th May, bls-signatures has no options to specify the wasm path on initialization. 31 | * So `initializeBLS()` always tries to load from the url: 32 | * - https://example.com/blsjs.wasm 33 | * If you can put blsjs.wasm into the root path like above, you can activate the line below. 34 | * However, with clvm >= 3.0.0, most of bls operations are run inside clvm_wasm_bg.wasm. 35 | * So I believe you don't need to load `blsjs.wasm` in most cases. 36 | */ 37 | // await initializeBLS(); 38 | 39 | const nodeToRender = document.getElementById('root'); 40 | if (!nodeToRender) { 41 | throw new Error('#root Element was not found'); 42 | } 43 | 44 | ReactDOM.createRoot(nodeToRender).render( 45 | 46 | 47 | 48 | ); 49 | } 50 | 51 | main().catch((error: unknown) => { 52 | console.error(error); 53 | }); 54 | -------------------------------------------------------------------------------- /example/typescript_react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": [ 6 | "ES2022", 7 | "DOM", 8 | "DOM.Iterable" 9 | ], 10 | "module": "ESNext", 11 | "skipLibCheck": true, 12 | /* Bundler mode */ 13 | "moduleResolution": "bundler", 14 | "allowImportingTsExtensions": true, 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true, 18 | "jsx": "react-jsx", 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noFallthroughCasesInSwitch": true 24 | }, 25 | "include": [ 26 | "src" 27 | ], 28 | "references": [ 29 | { 30 | "path": "./tsconfig.node.json" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /example/typescript_react/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": [ 10 | "vite.config.ts", 11 | "package.json" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /example/typescript_react/vite.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig, UserConfig} from 'vite'; 2 | import react from '@vitejs/plugin-react-swc'; 3 | 4 | export default defineConfig(() => { 5 | const SERVER_PORT = 5173; 6 | 7 | return { 8 | plugins: [react()], 9 | build: { 10 | outDir: '.dist', 11 | }, 12 | server: { 13 | port: SERVER_PORT, 14 | proxy: { 15 | '^.*(? `/public/assets/${path.replace(/.*((blsjs|clvm_wasm_bg).wasm)$/, '$1')}`, 21 | }, 22 | }, 23 | }, 24 | } as UserConfig; 25 | }); 26 | -------------------------------------------------------------------------------- /example/typescript_webpack/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | trim_trailing_whitespace=false 5 | insert_final_newline=false 6 | indent_style=space 7 | indent_size=2 8 | 9 | [{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}] 10 | indent_style=space 11 | indent_size=2 12 | 13 | [{.eslintrc,.prettierrc,.babelrc,jest.config,.stylelintrc,*.json,*.jsb3,*.jsb2,*.bowerrc}] 14 | indent_style=space 15 | indent_size=2 16 | 17 | [*.svg] 18 | indent_style=space 19 | indent_size=2 20 | 21 | [*.css] 22 | indent_style=space 23 | indent_size=2 24 | 25 | [*.scss] 26 | indent_style=space 27 | indent_size=2 28 | 29 | [*.styl] 30 | indent_style=space 31 | indent_size=2 32 | 33 | [*.js] 34 | indent_style=space 35 | indent_size=2 36 | 37 | [{*.ats,*.ts}] 38 | indent_style=space 39 | indent_size=2 40 | ij_typescript_import_use_node_resolution = false 41 | 42 | [*.tsx] 43 | indent_style=space 44 | indent_size=2 45 | ij_typescript_import_use_node_resolution = false 46 | 47 | [{*.mjs,*.es6}] 48 | indent_style=space 49 | indent_size=2 50 | 51 | [*.js.flow] 52 | indent_style=space 53 | indent_size=2 54 | 55 | [*.jsx] 56 | indent_style=space 57 | indent_size=2 58 | 59 | [{jshint.json,*.jshintrc}] 60 | indent_style=space 61 | indent_size=2 62 | 63 | [{tsconfig.e2e.json,tsconfig.spec.json,tsconfig.app.json,tsconfig.json}] 64 | indent_style=space 65 | indent_size=2 66 | 67 | [*.js.map] 68 | indent_style=space 69 | indent_size=2 70 | 71 | [{.analysis_options,*.yml,*.yaml}] 72 | indent_style=space 73 | indent_size=2 74 | 75 | -------------------------------------------------------------------------------- /example/typescript_webpack/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | dist/ 3 | node_modules 4 | -------------------------------------------------------------------------------- /example/typescript_webpack/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2024 Izumi Hoshino 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /example/typescript_webpack/README.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | This is an example code to use `clvm-js` with TypeScript and Webpack. 3 | 4 | # Setup 5 | ```shell 6 | # 0. Install pnpm 7 | # 1. Clone this repository 8 | git clone https://github.com/Chia-Mine/clvm-js 9 | cd clvm-js 10 | 11 | # 2. Build clvm-js 12 | pnpm i # or `yarn`, `npm i` 13 | pnpm build # or `yarn build`, `npm run build` 14 | 15 | # 3. Build example code for web browsers 16 | cd example/typescript_webpack 17 | pnpm i # or `yarn`, `npm i` 18 | pnpm build # or `yarn build`, `npm run build` 19 | 20 | # 4. Run demo server 21 | pnpm start # or `yarn start`, `npm start` 22 | ``` 23 | -------------------------------------------------------------------------------- /example/typescript_webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-clvm-with-browser", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack -c ./webpack.config.js", 8 | "start": "http-server .dist -o" 9 | }, 10 | "private": true, 11 | "dependencies": { 12 | "clvm": "^3.0.0" 13 | }, 14 | "devDependencies": { 15 | "copy-webpack-plugin": "^12.0.2", 16 | "css-loader": "^7.1.1", 17 | "html-webpack-plugin": "^5.6.0", 18 | "http-server": "^14.1.1", 19 | "sass": "^1.77.0", 20 | "sass-loader": "^14.2.1", 21 | "style-loader": "^4.0.0", 22 | "ts-loader": "^9.5.1", 23 | "typescript": "^5.4.5", 24 | "webpack": "^5.91.0", 25 | "webpack-cli": "^5.1.4" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/typescript_webpack/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | clvm sample 6 | 7 | 8 |
9 |

Chia-Mine/clvm-js

10 |
11 | 12 | 13 |
14 |
15 | 16 | 17 |
18 |
19 | 22 | 25 |
26 |
27 | 28 |

29 |   
30 |
31 | 32 | -------------------------------------------------------------------------------- /example/typescript_webpack/src/index.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | #root { 11 | width: 800px; 12 | margin: auto; 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | 18 | .input { 19 | label { 20 | display: block; 21 | margin-bottom: 4px; 22 | } 23 | input { 24 | width: 100%; 25 | } 26 | 27 | & + .input { 28 | margin-top: 16px; 29 | } 30 | } 31 | 32 | .buttons { 33 | margin-top: 16px; 34 | position: relative; 35 | button { 36 | padding: 4px 8px; 37 | } 38 | button + button { 39 | margin-left: 16px; 40 | } 41 | button:last-child { 42 | position: absolute; 43 | right: 0; 44 | } 45 | } 46 | 47 | .output-container { 48 | margin-top: 16px; 49 | 50 | #output { 51 | border: 1px solid #ccc; 52 | padding: 8px; 53 | white-space: pre-wrap; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /example/typescript_webpack/src/index.ts: -------------------------------------------------------------------------------- 1 | import "./index.scss"; 2 | import { 3 | initialize, 4 | h, 5 | SExp, 6 | run_chia_program, 7 | Flag, 8 | CLVMType, 9 | } from "clvm/browser"; 10 | 11 | window.addEventListener("load", async function onWindowLoad() { 12 | // The element which holds command result output 13 | const outputEl = document.getElementById("output") as HTMLElement; 14 | if (!outputEl) { 15 | throw new Error("output box was not found"); 16 | } 17 | const prgEl = document.getElementById("prg") as HTMLInputElement; 18 | if (!prgEl) { 19 | throw new Error("program input was not found"); 20 | } 21 | const envEl = document.getElementById("env") as HTMLInputElement; 22 | if (!envEl) { 23 | throw new Error("env input was not found"); 24 | } 25 | 26 | // Set initial values 27 | /* 28 | This comes from a block of height 600043 in the testnet11. 29 | You can get the block data by: 30 | chia rpc full_node get_blocks '{"start": 600043, "end": 600044}' 31 | on testnet11. 32 | The transactions_generator in the block was serialized with backref enabled. 33 | */ 34 | prgEl.value = "0xff01ff01ffffffa09dc16b766b557d0d8d94fe1ee636245b4417a46cd53bd4e70c26a62dc698d406ffff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04fffe84016b6b7fff80808080fffe820db78080fe81ffffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101fe6f80ff0180fe3e80ffff04ffff01b0a282b4b0117a8d04906835fa4fa0e13d3fbd1dd61899ebdf5157977611d1bae52f2ea97cbc3916466b1c9176d30a9030fe3f80ff85727e956731ffff80ffff01ffff33ffa05597ef68eaf171a6303995ecbb14fdbf2c24300b625bfbc886ea68270424661dff5880ffff33ffa07a7a9cb053b9e7086ddbb789a4a1abc646a06627d372eca59368bf90c15028bfff85727b9a765980ffff34ff8402faf08080ffff3cffa0d71f4c45af09583209498dbb9974bbda21b859fac0bf3348337ed33a2ba5c3838080ff808080808080"; 35 | envEl.value = "0x80"; 36 | 37 | /* 38 | * Initialize button's onclick handler 39 | */ 40 | const buttons = document.querySelectorAll(".buttons button:not(:last-child)") as NodeListOf; 41 | if (!buttons || buttons.length < 1) { 42 | throw new Error("Button was not found"); 43 | } 44 | buttons.forEach(b => { 45 | b.onclick = onButtonClicked; 46 | }); 47 | const clearButton = document.getElementById("clear-btn") as HTMLButtonElement; 48 | if (!clearButton) { 49 | throw new Error("Clear button was not found"); 50 | } 51 | clearButton.onclick = function () { 52 | outputEl.textContent = ""; 53 | } 54 | 55 | // Load blsjs.wasm and clvm_wasm_bg.wasm 56 | await initialize().catch(console.error); 57 | 58 | /** 59 | * onclick handler for buttons 60 | * @param {MouseEvent} e 61 | */ 62 | async function onButtonClicked(e: MouseEvent) { 63 | const buttonEl = (e.currentTarget as HTMLButtonElement); 64 | outputEl.textContent = ""; 65 | 66 | if (!prgEl.value || !envEl.value) { 67 | outputEl.textContent = "program or env is not specified"; 68 | return; 69 | } 70 | 71 | try { 72 | const prg = h(prgEl.value || "").raw(); 73 | const env = h(envEl.value || "").raw(); 74 | 75 | const result = run_chia_program(prg, env, 1000000000n, Flag.allow_backrefs()); 76 | const [cost, node] = result; 77 | const sexp = new SExp(node as CLVMType); 78 | const sexpStr = JSON.stringify(sexp, null, 2); 79 | outputEl.textContent = `Cost: ${cost}\r\nSExp: ${sexpStr}`; 80 | } catch (e: any) { 81 | console.error(e); 82 | if (e && typeof e === "object" && "name" in e && "message" in e) { 83 | const err = e as Error; 84 | outputEl.textContent = `${err.name}: ${err.message}`; 85 | } else { 86 | outputEl.textContent = typeof e === "string" ? e : JSON.stringify(e); 87 | } 88 | } 89 | } 90 | }); 91 | -------------------------------------------------------------------------------- /example/typescript_webpack/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./.dist", 4 | "module": "es6", 5 | "target": "es2022", 6 | "allowJs": false, 7 | "lib": [ 8 | "dom", 9 | "dom.iterable", 10 | "esnext" 11 | ], 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "sourceMap": true, 15 | "skipLibCheck": true, 16 | "noImplicitAny": true, 17 | "declaration": true, 18 | "newLine": "lf", 19 | "moduleResolution": "node", 20 | }, 21 | "include": [ 22 | "src/**/*", 23 | ], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /example/typescript_webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | const CopyPlugin = require("copy-webpack-plugin"); 4 | 5 | module.exports = { 6 | mode: "development", 7 | entry: "./src/index.ts", 8 | devtool: "source-map", 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.tsx?$/, 13 | use: "ts-loader", 14 | exclude: /node_modules/, 15 | }, 16 | { 17 | test: /\.s[ac]ss$/i, 18 | use: ["style-loader", "css-loader", "sass-loader"], 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: [".tsx", ".ts", ".js"], 24 | }, 25 | plugins: [ 26 | new HtmlWebpackPlugin({ 27 | template: "src/index.html" 28 | }), 29 | new CopyPlugin({ 30 | patterns: [ 31 | {from: path.resolve(__dirname, "..", "..", ".dist", "npm", "browser", "blsjs.wasm")}, 32 | {from: path.resolve(__dirname, "..", "..", ".dist", "npm", "browser", "clvm_wasm_bg.wasm")}, 33 | ] 34 | }), 35 | ], 36 | target: ["web"], 37 | output: { 38 | filename: "main.js", 39 | path: path.resolve(__dirname, ".dist"), 40 | clean: true, 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | testMatch: ["/tests/**/*_test.ts"], 6 | collectCoverage: false, 7 | transform: { 8 | "^.+\\.tsx?$": ["ts-jest", { 9 | tsconfig: { 10 | sourceMap: true, 11 | inlineSourceMap: true, 12 | allowJs: true, 13 | } 14 | }] 15 | }, 16 | modulePathIgnorePatterns: [ 17 | "/.dist", 18 | ], 19 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clvm", 3 | "version": "3.0.0", 4 | "author": "ChiaMineJP ", 5 | "description": "Javascript implementation of chia lisp", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/Chia-Mine/clvm-js.git" 10 | }, 11 | "bugs": "https://github.com/Chia-Mine/clvm-js/issues", 12 | "main": "./index.js", 13 | "typings": "./index.d.ts", 14 | "scripts": { 15 | "build": "node pre_build.js && tsc --sourcemap false && webpack", 16 | "test": "jest" 17 | }, 18 | "keywords": [ 19 | "chia", 20 | "clvm", 21 | "chialisp", 22 | "typescript" 23 | ], 24 | "engines": { 25 | "node": ">=12.13.0" 26 | }, 27 | "dependencies": { 28 | "bls-signatures": "^2.0.3", 29 | "clvm_wasm": "^0.7.0", 30 | "jscrypto": "^1.0.3" 31 | }, 32 | "devDependencies": { 33 | "@types/jest": "^29.5.12", 34 | "@types/node": "^20.12.10", 35 | "@typescript-eslint/eslint-plugin": "^7.8.0", 36 | "@typescript-eslint/parser": "^7.2.0", 37 | "eslint": "^8.57.0", 38 | "eslint-plugin-import": "^2.29.1", 39 | "eslint-plugin-jsdoc": "^48.2.3", 40 | "fork-ts-checker-webpack-plugin": "^9.0.2", 41 | "jest": "^29.7.0", 42 | "terser-webpack-plugin": "^5.3.10", 43 | "ts-jest": "^29.1.2", 44 | "ts-loader": "^9.5.1", 45 | "typescript": "^5.4.5", 46 | "webpack": "^5.91.0", 47 | "webpack-cli": "^5.1.4" 48 | }, 49 | "browserslist": [ 50 | "edge >= 79", 51 | "firefox >= 68", 52 | "chrome >= 67", 53 | "safari > 14", 54 | "opera >= 54", 55 | "ios_saf >= 14.4", 56 | "android >= 67", 57 | "op_mob >= 48", 58 | "and_chr >= 67", 59 | "and_ff >= 68", 60 | "samsung >= 9.2", 61 | "node >= 10.4.0", 62 | "electron >= 4.0.0" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /pre_build.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | 4 | // clean and create output dir 5 | const distDir = path.join(__dirname, ".dist"); 6 | if(fs.existsSync(distDir)){ 7 | fs.rmSync(distDir, {recursive: true}); 8 | } 9 | fs.mkdirSync(distDir); 10 | const distNpmDir = path.join(distDir, "npm"); 11 | fs.mkdirSync(distNpmDir); 12 | 13 | // Copy wasm file 14 | const browserDir = path.join(distNpmDir, "browser"); 15 | if(fs.existsSync(browserDir)){ 16 | fs.rmSync(browserDir, {recursive: true}); 17 | } 18 | fs.mkdirSync(browserDir); 19 | const blsWasmSrcPath = path.join(__dirname, "node_modules", "bls-signatures", "blsjs.wasm"); 20 | const blsWasmDestPath = path.join(browserDir, "blsjs.wasm"); 21 | if(!fs.existsSync(blsWasmSrcPath)){ 22 | console.error("blsjs.wasm not found at:"); 23 | console.error(blsWasmSrcPath); 24 | console.error("Probably you haven't execute npm install yet"); 25 | return; 26 | } 27 | fs.copyFileSync(blsWasmSrcPath, blsWasmDestPath); 28 | 29 | const clvmWasmSrcPath = path.join(__dirname, "node_modules", "clvm_wasm", "clvm_wasm_bg.wasm"); 30 | const clvmWasmDestPath = path.join(browserDir, "clvm_wasm_bg.wasm"); 31 | if (!fs.existsSync(clvmWasmSrcPath)) { 32 | console.error("clvm_wasm_bg.wasm not found at:"); 33 | console.error(clvmWasmSrcPath); 34 | console.error("Probably you haven't execute npm install yet"); 35 | return; 36 | } 37 | fs.copyFileSync(clvmWasmSrcPath, clvmWasmDestPath); 38 | 39 | const browserDtsPath = path.join(browserDir, "index.d.ts"); 40 | fs.writeFileSync(browserDtsPath, 'export * from "..";\n'); 41 | 42 | 43 | const packageJson = require("./package.json"); 44 | fs.writeFileSync(path.join(distNpmDir, "package.json"), JSON.stringify(packageJson, null, 2)); 45 | 46 | function copyFileToPublish(fileName){ 47 | const srcPath = path.join(__dirname, fileName); 48 | const distPath = path.join(distNpmDir, fileName); 49 | if(fs.existsSync(srcPath)){ 50 | fs.copyFileSync(srcPath, distPath); 51 | } 52 | } 53 | 54 | copyFileToPublish("CHANGELOG.md"); 55 | copyFileToPublish("LICENSE"); 56 | copyFileToPublish("README.md"); 57 | -------------------------------------------------------------------------------- /src/CLVMObject.ts: -------------------------------------------------------------------------------- 1 | import {None, Optional} from "./__python_types__"; 2 | import {Bytes, isTuple, Tuple} from "./__type_compatibility__"; 3 | import {EvalError} from "./EvalError"; 4 | 5 | export type CLVMType = { 6 | atom: Optional; 7 | pair: Optional>; 8 | }; 9 | export type Atom = { 10 | atom: Bytes; 11 | pair: None; 12 | }; 13 | export type Cons = { 14 | atom: None; 15 | pair: Tuple; 16 | }; 17 | 18 | /* 19 | This class implements the CLVM Object protocol in the simplest possible way, 20 | by just having an "atom" and a "pair" field 21 | */ 22 | export class CLVMObject implements CLVMType { 23 | private readonly _atom: Optional = None; 24 | private readonly _pair: Optional> = None; 25 | 26 | get atom(){ 27 | return this._atom; 28 | } 29 | get pair(){ 30 | return this._pair; 31 | } 32 | 33 | public constructor(v: any) { 34 | if(v instanceof CLVMObject){ 35 | this._atom = v.atom; 36 | this._pair = v.pair; 37 | } 38 | else if(v && (typeof v === "object" || typeof v === "function") && "atom" in v && "pair" in v){ 39 | this._atom = v.atom; 40 | this._pair = v.pair; 41 | } 42 | else if(isTuple(v)){ 43 | this._pair = v; 44 | this._atom = None; 45 | } 46 | else{ 47 | this._atom = v; 48 | this._pair = None; 49 | } 50 | } 51 | } 52 | 53 | export function isAtom(obj: CLVMType): obj is Atom { 54 | if((obj.atom && obj.pair) || (!obj.atom && !obj.pair)){ 55 | throw new EvalError("Invalid clvm", obj); 56 | } 57 | 58 | return Boolean(obj.atom && !obj.pair); 59 | } 60 | 61 | export function isCons(obj: CLVMType): obj is Cons { 62 | if((obj.atom && obj.pair) || (!obj.atom && !obj.pair)){ 63 | throw new EvalError("Invalid clvm", obj); 64 | } 65 | 66 | return Boolean((!obj.atom && obj.pair)); 67 | } 68 | -------------------------------------------------------------------------------- /src/EvalError.ts: -------------------------------------------------------------------------------- 1 | import type {CLVMType} from "./CLVMObject"; 2 | 3 | export class EvalError extends Error { 4 | public _sexp?: CLVMType; 5 | name = "EvalError"; 6 | 7 | public constructor(message: string, sexp: CLVMType) { 8 | super(message); 9 | this._sexp = sexp; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SExp.ts: -------------------------------------------------------------------------------- 1 | import type {G1Element} from "bls-signatures"; 2 | import {None, Optional} from "./__python_types__"; 3 | import {CLVMObject, CLVMType} from "./CLVMObject"; 4 | import {Bytes, isIterable, Tuple, t, Stream, isBytes, isTuple} from "./__type_compatibility__"; 5 | import {bigint_from_bytes, bigint_to_bytes, int_from_bytes, int_to_bytes} from "./casts"; 6 | import {sexp_to_stream} from "./serialize"; 7 | import {as_javascript} from "./as_javascript"; 8 | import {EvalError} from "./EvalError"; 9 | 10 | export type CastableType = SExp 11 | | CLVMType 12 | | Bytes 13 | | string 14 | | number 15 | | bigint 16 | | None 17 | | G1Element 18 | | CastableType[] 19 | | Tuple 20 | ; 21 | 22 | export function looks_like_clvm_object(o: any): o is CLVMType { 23 | if(!o || (typeof o !== "object" && typeof o !== "function")){ 24 | return false; 25 | } 26 | 27 | return Boolean("atom" in o && "pair" in o); 28 | } 29 | 30 | // this function recognizes some common types and turns them into plain bytes 31 | export function convert_atom_to_bytes(v: any): Bytes { 32 | if(isBytes(v)){ 33 | return v; 34 | } 35 | else if(typeof v === "string"){ 36 | return Bytes.from(v, "utf8"); 37 | } 38 | else if(typeof v === "number"){ 39 | return int_to_bytes(v, {signed: true}); 40 | } 41 | else if(typeof v === "boolean"){ // Tips. In Python, isinstance(True, int) == True. 42 | return int_to_bytes(v ? 1 : 0, {signed: true}); 43 | } 44 | else if(typeof v === "bigint"){ 45 | return bigint_to_bytes(v, {signed: true}); 46 | } 47 | else if(v === None || !v){ 48 | return Bytes.NULL; 49 | } 50 | else if(isIterable(v)){ 51 | if(v.length > 0){ 52 | throw new Error(`can't cast ${JSON.stringify(v)} to bytes`); 53 | } 54 | return Bytes.NULL 55 | } 56 | else if(typeof v.serialize === "function"){ 57 | return Bytes.from(v, "G1Element"); 58 | } 59 | else if(typeof v.toBytes === "function"){ 60 | return v.toBytes() as Bytes; 61 | } 62 | 63 | try { 64 | return Bytes.from(v); 65 | } 66 | catch (_) { 67 | throw new Error(`can't cast ${JSON.stringify(v)} to bytes`); 68 | } 69 | } 70 | 71 | const op_convert = 0; 72 | const op_set_left = 1; 73 | const op_set_right = 2; 74 | const op_prepend_list = 3; 75 | type operations = typeof op_convert | typeof op_set_left | typeof op_set_right | typeof op_prepend_list; 76 | type op_target = number | None; 77 | type op_and_target = Tuple; 78 | 79 | export function to_sexp_type(value: CastableType): CLVMType { 80 | let v: CastableType = value; 81 | const stack = [v]; 82 | 83 | const ops: op_and_target[] = [t(0, None)]; 84 | 85 | while(ops.length){ 86 | const item = (ops.pop() as op_and_target); 87 | const op = item[0]; 88 | let targetIndex = item[1]; 89 | 90 | // convert value 91 | if(op === op_convert){ 92 | if(looks_like_clvm_object(stack[stack.length-1])){ 93 | continue; 94 | } 95 | 96 | v = stack.pop(); 97 | if(isTuple(v)){ 98 | if(v.length !== 2){ 99 | throw new Error(`can't cast tuple of size ${v.length}`); 100 | } 101 | const [left, right] = v; 102 | targetIndex = stack.length; 103 | stack.push(new CLVMObject(t(left, right))); 104 | 105 | if(!looks_like_clvm_object(right)){ 106 | stack.push(right); 107 | ops.push(t(2, targetIndex)); // set right 108 | ops.push(t(0, None)); // convert 109 | } 110 | 111 | if(!looks_like_clvm_object(left)){ 112 | stack.push(left); 113 | ops.push(t(1, targetIndex)); 114 | ops.push(t(0, None)); 115 | } 116 | 117 | continue; 118 | } 119 | else if(Array.isArray(v) /* && !(v instance of Tuple) */){ 120 | targetIndex = stack.length; 121 | stack.push(new CLVMObject(Bytes.NULL)); 122 | 123 | for(const _ of v){ 124 | stack.push(_ as CLVMType); 125 | ops.push(t(3, targetIndex)); // prepend list 126 | // we only need to convert if it's not already the right type 127 | if(!looks_like_clvm_object(_)){ 128 | ops.push(t(0, None)); // convert 129 | } 130 | } 131 | continue; 132 | } 133 | 134 | stack.push(new CLVMObject(convert_atom_to_bytes(v))); 135 | continue; 136 | } 137 | 138 | if(targetIndex === None){ 139 | throw new Error("Invalid target. target is None"); 140 | } 141 | 142 | if (op === op_set_left){ // set left 143 | stack[targetIndex] = new CLVMObject(t( 144 | new CLVMObject(stack.pop()), 145 | ((stack[targetIndex] as CLVMType).pair as Tuple)[1] 146 | )); 147 | } 148 | else if(op === op_set_right){ // set right 149 | stack[targetIndex] = new CLVMObject(t( 150 | ((stack[targetIndex] as CLVMType).pair as Tuple)[0], 151 | new CLVMObject(stack.pop()) 152 | )); 153 | } 154 | else if(op === op_prepend_list){ // prepend list 155 | stack[targetIndex] = new CLVMObject(t(stack.pop(), stack[targetIndex])); 156 | } 157 | } 158 | 159 | // there's exactly one item left at this point 160 | if(stack.length !== 1){ 161 | throw new Error("internal error"); 162 | } 163 | 164 | // stack[0] implements the clvm object protocol and can be wrapped by an SExp 165 | return stack[0] as CLVMType; 166 | } 167 | 168 | /* 169 | SExp provides higher level API on top of any object implementing the CLVM 170 | object protocol. 171 | The tree of values is not a tree of SExp objects, it's a tree of CLVMObject 172 | like objects. SExp simply wraps them to provide a uniform view of any 173 | underlying conforming tree structure. 174 | 175 | The CLVM object protocol (concept) exposes two attributes: 176 | 1. "atom" which is either None or bytes 177 | 2. "pair" which is either None or a tuple of exactly two elements. Both 178 | elements implementing the CLVM object protocol. 179 | Exactly one of "atom" and "pair" must be None. 180 | */ 181 | export class SExp implements CLVMType { 182 | // When instantiating SExp from `LazyNode` of `clvm_wasm`, _atom will be `Optional`. 183 | // Otherwise, it will be `Optional` 184 | private readonly _atom: Optional = None; 185 | private readonly _pair: Optional> = None; 186 | 187 | get atom(): Optional { 188 | return this._atom instanceof Uint8Array ? new Bytes(this._atom) : this._atom; 189 | } 190 | get pair(): Optional>{ 191 | return this._pair; 192 | } 193 | 194 | static readonly TRUE: SExp = new SExp(new CLVMObject(Bytes.from("0x01", "hex"))); 195 | static readonly FALSE: SExp = new SExp(new CLVMObject(Bytes.NULL)); 196 | static readonly __NULL__: SExp = new SExp(new CLVMObject(Bytes.NULL)); 197 | 198 | static to(v: CastableType): SExp { 199 | if(v instanceof SExp){ 200 | return v; 201 | } 202 | 203 | if(looks_like_clvm_object(v)){ 204 | return new SExp(v); 205 | } 206 | 207 | // this will lazily convert elements 208 | return new SExp(to_sexp_type(v)); 209 | } 210 | 211 | static null(){ 212 | return SExp.__NULL__; 213 | } 214 | 215 | public constructor(v: CLVMType) { 216 | this._atom = v.atom; 217 | this._pair = v.pair; 218 | } 219 | 220 | public as_pair(): Tuple|None { 221 | const pair = this.pair; 222 | if(pair === None){ 223 | return pair; 224 | } 225 | return t(new SExp(pair[0]), new SExp(pair[1])); 226 | } 227 | 228 | public listp(){ 229 | return this.pair !== None; 230 | } 231 | 232 | public nullp(){ 233 | return this.atom !== None && this.atom.length === 0; 234 | } 235 | 236 | public as_int(){ 237 | return int_from_bytes(this.atom, {signed: true}); 238 | } 239 | 240 | public as_bigint(){ 241 | return bigint_from_bytes(this.atom, {signed: true}); 242 | } 243 | 244 | public as_bin(){ 245 | const f = new Stream(); 246 | sexp_to_stream(this, f); 247 | return f.getValue(); 248 | } 249 | 250 | public cons(right: any){ 251 | return SExp.to(t(this, right)); 252 | } 253 | 254 | public first(){ 255 | const pair = this.pair; 256 | if(pair){ 257 | return new SExp(pair[0]); 258 | } 259 | throw new EvalError("first of non-cons", this); 260 | } 261 | 262 | public rest(){ 263 | const pair = this.pair; 264 | if(pair){ 265 | return new SExp(pair[1]); 266 | } 267 | throw new EvalError("rest of non-cons", this); 268 | } 269 | 270 | public *as_iter(){ 271 | let v: SExp = this; 272 | while(!v.nullp()){ 273 | yield v.first(); 274 | v = v.rest(); 275 | } 276 | } 277 | 278 | public equal_to(other: any/* CastableType */): boolean { 279 | try{ 280 | other = SExp.to(other); 281 | const to_compare_stack = [t(this, other)] as Array>; 282 | while(to_compare_stack.length){ 283 | const [s1, s2] = (to_compare_stack.pop() as Tuple); 284 | const p1 = s1.as_pair(); 285 | if(p1){ 286 | const p2 = s2.as_pair(); 287 | if(p2){ 288 | to_compare_stack.push(t(p1[0], p2[0])); 289 | to_compare_stack.push(t(p1[1], p2[1])); 290 | } 291 | else{ 292 | return false; 293 | } 294 | } 295 | else if(s2.as_pair() || !(s1.atom && s2.atom && s1.atom.equal_to(s2.atom))){ 296 | return false; 297 | } 298 | } 299 | return true; 300 | } 301 | catch(e){ 302 | return false; 303 | } 304 | } 305 | 306 | public list_len(){ 307 | let v: SExp = this; 308 | let size = 0; 309 | while(v.listp()){ 310 | size += 1; 311 | v = v.rest(); 312 | } 313 | return size; 314 | } 315 | 316 | public as_javascript(){ 317 | return as_javascript(this); 318 | } 319 | 320 | public toString(){ 321 | return this.as_bin().hex(); 322 | } 323 | 324 | public toJSON(){ 325 | if(this.pair){ 326 | return this.pair; 327 | } 328 | if(this.atom){ 329 | return this.atom.hex(); 330 | } 331 | throw new EvalError("Invalid object", this); 332 | } 333 | 334 | public __repr__(){ 335 | return `SExp(${this.as_bin().hex()})`; 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /src/__bls_signatures__/index.ts: -------------------------------------------------------------------------------- 1 | import type {G1Element as G1ElementType, ModuleInstance} from "bls-signatures"; 2 | import * as blsLoader from "./loader"; 3 | 4 | type TCreateModule = () => Promise; 5 | let BLS: ModuleInstance | undefined; 6 | let loadPromise: Promise | undefined; 7 | 8 | /** 9 | * Load BLS Module instance. 10 | * This function must be called an awaited on program start up. 11 | */ 12 | export async function initializeBLS(): Promise { 13 | if (BLS) { 14 | return BLS; 15 | } else if (loadPromise) { 16 | return loadPromise; 17 | } 18 | 19 | return loadPromise = new Promise((resolve, reject) => { 20 | if (BLS) { 21 | loadPromise = undefined; 22 | return resolve(BLS); 23 | } 24 | 25 | ((blsLoader as unknown) as TCreateModule)().then((instance) => { 26 | if(!instance){ 27 | return reject(); 28 | } 29 | loadPromise = undefined; 30 | return resolve(BLS = instance); 31 | }).catch(e => { 32 | console.error("Error while loading BLS module"); 33 | return reject(e); 34 | }); 35 | }); 36 | } 37 | 38 | /** 39 | * This function must be called after `initializeBLS()` is done. 40 | * Calling `await initializeBLS()` on program startup is library user's responsibility. 41 | * 42 | * This is used for synchronous code execution. 43 | * Within this library, this is always called to get BLS module to keep code synchronous. 44 | */ 45 | export function getBLSModule() { 46 | if (!BLS) { 47 | throw new Error("BLS module has not been loaded. Please call `await initializeBLS()` on start up"); 48 | } 49 | 50 | return BLS; 51 | } 52 | 53 | export function G1Element_from_bytes(bytes: Uint8Array) { 54 | assert_G1Element_valid(bytes); 55 | const BLSModule = getBLSModule(); 56 | try { 57 | return BLSModule.G1Element.from_bytes(bytes); 58 | } catch (e) { 59 | // Print exception message if debug module is enabled and loaded. 60 | const message = "Exception in G1Element operation"; 61 | /* 62 | const get_exception_message = BLS.Util.get_exception_message; 63 | if (typeof get_exception_message === "function") { 64 | message = get_exception_message(e as number); 65 | } 66 | */ 67 | throw new Error(message); 68 | } 69 | } 70 | 71 | export function assert_G1Element_valid(bytes: Uint8Array){ 72 | const BLSModule = getBLSModule(); 73 | const {G1Element} = BLSModule; 74 | if(bytes.length !== G1Element.SIZE){ 75 | throw new Error("Length of bytes object not equal to G1Element::SIZE"); 76 | } 77 | 78 | if((bytes[0] & 0xc0) === 0xc0){ // representing infinity 79 | if(bytes[0] !== 0xc0){ 80 | throw new Error("G1Element: Given G1 infinity element must be canonical"); 81 | } 82 | for(let i=1;i Promise; 3 | export default TCreateModule; 4 | -------------------------------------------------------------------------------- /src/__bls_signatures__/loader.js: -------------------------------------------------------------------------------- 1 | const createModuleInstance = require("bls-signatures"); 2 | module.exports = createModuleInstance; 3 | -------------------------------------------------------------------------------- /src/__clvm_wasm__.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * How to generate this file. 3 | * 1. Generate js interface files 4 | * 1.1. git clone https://github.com/Chia-Network/clvm_rs 5 | * 1.2. cd clvm_rs/wasm 6 | * 1.3. cargo install wasm-pack 7 | * 1.4. wasm-pack build --release --target=bundler 8 | * 2. Preserve loader code embedded below. 9 | * 3. Check contents of ./pkg/clvm_wasm_bg.wasm.d.ts and compose `ClvmWasmExports` type from it. 10 | * 3. Copy contents of ./pkg/clvm_wasm_bg.js and paste here 11 | * 4. Annotate typings, fix lint issues 12 | * 5. Paste loader code preserved in the previous procedure 13 | * 6. Add `__wb*` functions to the `imports` object. 14 | * 7. Add `toJSON()` method to `LazyNode`. 15 | */ 16 | import {Word32Array} from "jscrypto/Word32Array"; 17 | 18 | type ClvmWasmExports = { 19 | memory: WebAssembly.Memory; 20 | serialized_length(a: number, b: number, c: number): void; 21 | node_from_bytes(a: number, b: number, c: number, d: number): void; 22 | __wbg_flag_free(a: number): void; 23 | flag_no_unknown_ops(): number; 24 | flag_allow_backrefs(): number; 25 | run_clvm(a: number, b: number, c: number, d: number, e: number, f: number): void; 26 | run_chia_program(a: number, b: number, c: number, d: number, e: number, f: bigint, g: number): void; 27 | __wbg_lazynode_free(a: number): void; 28 | lazynode_pair(a: number): number; 29 | lazynode_atom(a: number, b: number): void; 30 | lazynode_to_bytes_with_backref(a: number, b: number): void; 31 | lazynode_to_bytes(a: number, b: number, c: number): void; 32 | lazynode_from_bytes_with_backref(a: number, b: number, c: number): void; 33 | lazynode_from_bytes(a: number, b: number, c: number): void; 34 | __wbindgen_add_to_stack_pointer(a: number): number; 35 | __wbindgen_malloc(a: number, b: number): number; 36 | __wbindgen_free(a: number, b: number, c?: number): void; 37 | } 38 | 39 | const imports: WebAssembly.Imports = {}; 40 | let wasm: ClvmWasmExports; 41 | 42 | 43 | const lTextDecoder = typeof TextDecoder === "undefined" ? (0, module.require)("util").TextDecoder : TextDecoder; 44 | 45 | const cachedTextDecoder = new lTextDecoder("utf-8", {ignoreBOM: true, fatal: true}); 46 | 47 | cachedTextDecoder.decode(); 48 | 49 | let cachedUint8Memory0: Uint8Array|null = null; 50 | 51 | function getUint8Memory0() { 52 | if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { 53 | cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); 54 | } 55 | return cachedUint8Memory0; 56 | } 57 | 58 | function getStringFromWasm0(ptr: number, len: number) { 59 | ptr = ptr >>> 0; 60 | return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); 61 | } 62 | 63 | const heap = new Array(128).fill(undefined); 64 | 65 | heap.push(undefined, null, true, false); 66 | 67 | let heap_next = heap.length; 68 | 69 | function addHeapObject(obj: any) { 70 | if (heap_next === heap.length) heap.push(heap.length + 1); 71 | const idx = heap_next; 72 | heap_next = heap[idx]; 73 | 74 | heap[idx] = obj; 75 | return idx; 76 | } 77 | 78 | let WASM_VECTOR_LEN = 0; 79 | 80 | function passArray8ToWasm0(arg: Uint8Array, malloc: (size: number, align: number) => number) { 81 | const ptr = malloc(arg.length * 1, 1) >>> 0; 82 | getUint8Memory0().set(arg, ptr / 1); 83 | WASM_VECTOR_LEN = arg.length; 84 | return ptr; 85 | } 86 | 87 | let cachedBigInt64Memory0: BigInt64Array|null = null; 88 | 89 | function getBigInt64Memory0() { 90 | if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) { 91 | cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer); 92 | } 93 | return cachedBigInt64Memory0; 94 | } 95 | 96 | let cachedInt32Memory0: Int32Array|null = null; 97 | 98 | function getInt32Memory0() { 99 | if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { 100 | cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); 101 | } 102 | return cachedInt32Memory0; 103 | } 104 | 105 | function getObject(idx: number) { 106 | return heap[idx]; 107 | } 108 | 109 | function dropObject(idx: number) { 110 | if (idx < 132) return; 111 | heap[idx] = heap_next; 112 | heap_next = idx; 113 | } 114 | 115 | function takeObject(idx: number) { 116 | const ret = getObject(idx); 117 | dropObject(idx); 118 | return ret; 119 | } 120 | 121 | /** 122 | * @param {Uint8Array} program 123 | * @returns {bigint} 124 | */ 125 | export function serialized_length(program: Uint8Array): bigint { 126 | try { 127 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 128 | const ptr0 = passArray8ToWasm0(program, wasm.__wbindgen_malloc); 129 | const len0 = WASM_VECTOR_LEN; 130 | wasm.serialized_length(retptr, ptr0, len0); 131 | const r0 = getBigInt64Memory0()[retptr / 8 + 0]; 132 | const r2 = getInt32Memory0()[retptr / 4 + 2]; 133 | const r3 = getInt32Memory0()[retptr / 4 + 3]; 134 | if (r3) { 135 | throw takeObject(r2); 136 | } 137 | return BigInt.asUintN(64, r0); 138 | } finally { 139 | wasm.__wbindgen_add_to_stack_pointer(16); 140 | } 141 | } 142 | 143 | /** 144 | * @param {Uint8Array} b 145 | * @param {number} flag 146 | * @returns {LazyNode} 147 | */ 148 | export function node_from_bytes(b: Uint8Array, flag: number): LazyNode { 149 | try { 150 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 151 | const ptr0 = passArray8ToWasm0(b, wasm.__wbindgen_malloc); 152 | const len0 = WASM_VECTOR_LEN; 153 | wasm.node_from_bytes(retptr, ptr0, len0, flag); 154 | const r0 = getInt32Memory0()[retptr / 4 + 0]; 155 | const r1 = getInt32Memory0()[retptr / 4 + 1]; 156 | const r2 = getInt32Memory0()[retptr / 4 + 2]; 157 | if (r2) { 158 | throw takeObject(r1); 159 | } 160 | return LazyNode.__wrap(r0); 161 | } finally { 162 | wasm.__wbindgen_add_to_stack_pointer(16); 163 | } 164 | } 165 | 166 | function getArrayU8FromWasm0(ptr: number, len: number) { 167 | ptr = ptr >>> 0; 168 | return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); 169 | } 170 | 171 | /** 172 | * @param {Uint8Array} program 173 | * @param {Uint8Array} args 174 | * @param {number} flag 175 | * @returns {Uint8Array} 176 | */ 177 | export function run_clvm(program: Uint8Array, args: Uint8Array, flag: number): Uint8Array { 178 | try { 179 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 180 | const ptr0 = passArray8ToWasm0(program, wasm.__wbindgen_malloc); 181 | const len0 = WASM_VECTOR_LEN; 182 | const ptr1 = passArray8ToWasm0(args, wasm.__wbindgen_malloc); 183 | const len1 = WASM_VECTOR_LEN; 184 | wasm.run_clvm(retptr, ptr0, len0, ptr1, len1, flag); 185 | const r0 = getInt32Memory0()[retptr / 4 + 0]; 186 | const r1 = getInt32Memory0()[retptr / 4 + 1]; 187 | const v3 = getArrayU8FromWasm0(r0, r1).slice(); 188 | wasm.__wbindgen_free(r0, r1 * 1); 189 | return v3; 190 | } finally { 191 | wasm.__wbindgen_add_to_stack_pointer(16); 192 | } 193 | } 194 | 195 | /** 196 | * @param {Uint8Array} program 197 | * @param {Uint8Array} args 198 | * @param {bigint} max_cost 199 | * @param {number} flag 200 | * @returns {[bigint, any]} 201 | */ 202 | export function run_chia_program( 203 | program: Uint8Array, 204 | args: Uint8Array, 205 | max_cost: bigint, 206 | flag: number, 207 | ): [bigint, LazyNode] { 208 | try { 209 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 210 | const ptr0 = passArray8ToWasm0(program, wasm.__wbindgen_malloc); 211 | const len0 = WASM_VECTOR_LEN; 212 | const ptr1 = passArray8ToWasm0(args, wasm.__wbindgen_malloc); 213 | const len1 = WASM_VECTOR_LEN; 214 | wasm.run_chia_program(retptr, ptr0, len0, ptr1, len1, max_cost, flag); 215 | const r0 = getInt32Memory0()[retptr / 4 + 0]; 216 | const r1 = getInt32Memory0()[retptr / 4 + 1]; 217 | const r2 = getInt32Memory0()[retptr / 4 + 2]; 218 | if (r2) { 219 | throw takeObject(r1); 220 | } 221 | return takeObject(r0); 222 | } finally { 223 | wasm.__wbindgen_add_to_stack_pointer(16); 224 | } 225 | } 226 | 227 | /** 228 | */ 229 | export class Flag { 230 | __wbg_ptr = 0; 231 | 232 | __destroy_into_raw() { 233 | const ptr = this.__wbg_ptr; 234 | this.__wbg_ptr = 0; 235 | 236 | return ptr; 237 | } 238 | 239 | free() { 240 | const ptr = this.__destroy_into_raw(); 241 | wasm.__wbg_flag_free(ptr); 242 | } 243 | 244 | /** 245 | * @returns {number} 246 | */ 247 | static no_unknown_ops(): number { 248 | const ret = wasm.flag_no_unknown_ops(); 249 | return ret >>> 0; 250 | } 251 | 252 | /** 253 | * @returns {number} 254 | */ 255 | static allow_backrefs(): number { 256 | const ret = wasm.flag_allow_backrefs(); 257 | return ret >>> 0; 258 | } 259 | } 260 | 261 | /** 262 | */ 263 | export class LazyNode { 264 | __wbg_ptr = 0; 265 | 266 | static __wrap(ptr: number) { 267 | ptr = ptr >>> 0; 268 | const obj = Object.create(LazyNode.prototype); 269 | obj.__wbg_ptr = ptr; 270 | 271 | return obj; 272 | } 273 | 274 | __destroy_into_raw() { 275 | const ptr = this.__wbg_ptr; 276 | this.__wbg_ptr = 0; 277 | 278 | return ptr; 279 | } 280 | 281 | free() { 282 | const ptr = this.__destroy_into_raw(); 283 | wasm.__wbg_lazynode_free(ptr); 284 | } 285 | 286 | /** 287 | * @returns {Array | undefined} 288 | */ 289 | get pair(): [LazyNode, LazyNode] | undefined { 290 | const ret = wasm.lazynode_pair(this.__wbg_ptr); 291 | return takeObject(ret); 292 | } 293 | 294 | /** 295 | * @returns {Uint8Array | undefined} 296 | */ 297 | get atom(): Uint8Array | undefined { 298 | try { 299 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 300 | wasm.lazynode_atom(retptr, this.__wbg_ptr); 301 | const r0 = getInt32Memory0()[retptr / 4 + 0]; 302 | const r1 = getInt32Memory0()[retptr / 4 + 1]; 303 | let v1; 304 | if (r0 !== 0) { 305 | v1 = getArrayU8FromWasm0(r0, r1).slice(); 306 | wasm.__wbindgen_free(r0, r1 * 1); 307 | } 308 | return v1; 309 | } finally { 310 | wasm.__wbindgen_add_to_stack_pointer(16); 311 | } 312 | } 313 | 314 | /** 315 | * @returns {Uint8Array} 316 | */ 317 | to_bytes_with_backref(): Uint8Array { 318 | try { 319 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 320 | wasm.lazynode_to_bytes_with_backref(retptr, this.__wbg_ptr); 321 | const r0 = getInt32Memory0()[retptr / 4 + 0]; 322 | const r1 = getInt32Memory0()[retptr / 4 + 1]; 323 | const r2 = getInt32Memory0()[retptr / 4 + 2]; 324 | const r3 = getInt32Memory0()[retptr / 4 + 3]; 325 | if (r3) { 326 | throw takeObject(r2); 327 | } 328 | const v1 = getArrayU8FromWasm0(r0, r1).slice(); 329 | wasm.__wbindgen_free(r0, r1 * 1); 330 | return v1; 331 | } finally { 332 | wasm.__wbindgen_add_to_stack_pointer(16); 333 | } 334 | } 335 | 336 | /** 337 | * @param {number} limit 338 | * @returns {Uint8Array} 339 | */ 340 | to_bytes(limit: number): Uint8Array { 341 | try { 342 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 343 | wasm.lazynode_to_bytes(retptr, this.__wbg_ptr, limit); 344 | const r0 = getInt32Memory0()[retptr / 4 + 0]; 345 | const r1 = getInt32Memory0()[retptr / 4 + 1]; 346 | const r2 = getInt32Memory0()[retptr / 4 + 2]; 347 | const r3 = getInt32Memory0()[retptr / 4 + 3]; 348 | if (r3) { 349 | throw takeObject(r2); 350 | } 351 | const v1 = getArrayU8FromWasm0(r0, r1).slice(); 352 | wasm.__wbindgen_free(r0, r1 * 1); 353 | return v1; 354 | } finally { 355 | wasm.__wbindgen_add_to_stack_pointer(16); 356 | } 357 | } 358 | 359 | /** 360 | * @param {Uint8Array} b 361 | * @returns {LazyNode} 362 | */ 363 | static from_bytes_with_backref(b: Uint8Array): LazyNode { 364 | try { 365 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 366 | const ptr0 = passArray8ToWasm0(b, wasm.__wbindgen_malloc); 367 | const len0 = WASM_VECTOR_LEN; 368 | wasm.lazynode_from_bytes_with_backref(retptr, ptr0, len0); 369 | const r0 = getInt32Memory0()[retptr / 4 + 0]; 370 | const r1 = getInt32Memory0()[retptr / 4 + 1]; 371 | const r2 = getInt32Memory0()[retptr / 4 + 2]; 372 | if (r2) { 373 | throw takeObject(r1); 374 | } 375 | return LazyNode.__wrap(r0); 376 | } finally { 377 | wasm.__wbindgen_add_to_stack_pointer(16); 378 | } 379 | } 380 | 381 | /** 382 | * @param {Uint8Array} b 383 | * @returns {LazyNode} 384 | */ 385 | static from_bytes(b: Uint8Array): LazyNode { 386 | try { 387 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 388 | const ptr0 = passArray8ToWasm0(b, wasm.__wbindgen_malloc); 389 | const len0 = WASM_VECTOR_LEN; 390 | wasm.lazynode_from_bytes(retptr, ptr0, len0); 391 | const r0 = getInt32Memory0()[retptr / 4 + 0]; 392 | const r1 = getInt32Memory0()[retptr / 4 + 1]; 393 | const r2 = getInt32Memory0()[retptr / 4 + 2]; 394 | if (r2) { 395 | throw takeObject(r1); 396 | } 397 | return LazyNode.__wrap(r0); 398 | } finally { 399 | wasm.__wbindgen_add_to_stack_pointer(16); 400 | } 401 | } 402 | 403 | toJSON() { 404 | if(this.pair){ 405 | return this.pair; 406 | } 407 | if(this.atom){ 408 | return (new Word32Array(this.atom)).toString(); 409 | } 410 | throw new Error("Invalid object"); 411 | } 412 | } 413 | 414 | export function __wbg_lazynode_new(arg0: number) { 415 | const ret = LazyNode.__wrap(arg0); 416 | return addHeapObject(ret); 417 | } 418 | 419 | export function __wbindgen_string_new(arg0: number, arg1: number) { 420 | const ret = getStringFromWasm0(arg0, arg1); 421 | return addHeapObject(ret); 422 | } 423 | 424 | export function __wbindgen_bigint_from_u64(arg0: bigint) { 425 | const ret = BigInt.asUintN(64, arg0); 426 | return addHeapObject(ret); 427 | } 428 | 429 | export function __wbg_newwithlength_3ec098a360da1909(arg0: number) { 430 | const ret = new Array(arg0 >>> 0); 431 | return addHeapObject(ret); 432 | } 433 | 434 | export function __wbg_set_502d29070ea18557(arg0: number, arg1: number, arg2: number) { 435 | getObject(arg0)[arg1 >>> 0] = takeObject(arg2); 436 | } 437 | 438 | export function __wbindgen_throw(arg0: number, arg1: number) { 439 | throw new Error(getStringFromWasm0(arg0, arg1)); 440 | } 441 | 442 | 443 | // Loader part 444 | imports["__wbindgen_placeholder__"] = { 445 | __wbg_lazynode_new, 446 | __wbindgen_string_new, 447 | __wbindgen_bigint_from_u64, 448 | __wbg_newwithlength_3ec098a360da1909, 449 | __wbg_set_502d29070ea18557, 450 | __wbindgen_throw, 451 | }; 452 | 453 | const defaultClvmRsWasmPath = (() => { 454 | if (typeof document !== "undefined" && document.currentScript) { 455 | const scriptDir = (document.currentScript as HTMLScriptElement).src.replace(/\/[^/]+$/, ""); 456 | return scriptDir + "/clvm_wasm_bg.wasm"; 457 | } 458 | return "./clvm_wasm_bg.wasm"; 459 | })(); 460 | 461 | export type TInitOption = { 462 | pathToWasm?: string; 463 | fetchOption?: RequestInit; 464 | } 465 | 466 | export async function initializeClvmWasm(option?: TInitOption) { 467 | if (typeof window === "undefined") { 468 | // eslint-disable-next-line @typescript-eslint/no-var-requires 469 | const path = require.resolve("clvm_wasm/clvm_wasm_bg.wasm"); 470 | // eslint-disable-next-line @typescript-eslint/no-var-requires 471 | const bytes = require("fs").readFileSync(path); 472 | 473 | const wasmModule = new WebAssembly.Module(bytes); 474 | const wasmInstance = new WebAssembly.Instance(wasmModule, imports); 475 | wasm = wasmInstance.exports as ClvmWasmExports; 476 | } else { 477 | let url; 478 | if (option && option.pathToWasm) { 479 | url = option.pathToWasm; 480 | } else { 481 | url = defaultClvmRsWasmPath; 482 | } 483 | const fetcher = fetch(url, option && option.fetchOption); 484 | const wasmInstance = await WebAssembly.instantiateStreaming(fetcher, imports); 485 | wasm = wasmInstance.instance.exports as ClvmWasmExports; 486 | } 487 | } 488 | -------------------------------------------------------------------------------- /src/__debug__.ts: -------------------------------------------------------------------------------- 1 | import {SExp} from "./SExp"; 2 | 3 | export function prettyPrint(enable: boolean){ 4 | if(enable){ 5 | const toString = SExp.prototype.toString; 6 | SExp.prototype.toString = SExp.prototype.__repr__; 7 | SExp.prototype.__repr__ = toString; 8 | } 9 | else{ 10 | const __repr__ = SExp.prototype.toString; 11 | SExp.prototype.toString = SExp.prototype.__repr__; 12 | SExp.prototype.__repr__ = __repr__; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/__python_types__.ts: -------------------------------------------------------------------------------- 1 | /* 2 | export type int = number; 3 | export type int8 = number; 4 | export type uint8 = number; 5 | export type int16 = number; 6 | export type uint16 = number; 7 | export type int32 = number; 8 | export type uint32 = number; 9 | export type int64 = BigInt; 10 | export type uint64 = BigInt; 11 | export type uint128 = BigInt; 12 | export type int512 = BigInt; 13 | export type float = number; 14 | export type str = string; 15 | export type bool = boolean; 16 | export type True = true; 17 | export type False = false; 18 | */ 19 | export const None = undefined; 20 | // eslint-disable-next-line no-redeclare 21 | export type None = undefined; 22 | 23 | export type Optional = T | None; 24 | -------------------------------------------------------------------------------- /src/__type_compatibility__.ts: -------------------------------------------------------------------------------- 1 | import {Hex} from "jscrypto/Hex"; 2 | import {Utf8} from "jscrypto/Utf8"; 3 | import {Word32Array} from "jscrypto/Word32Array"; 4 | import {SHA256} from "jscrypto/SHA256"; 5 | import {None} from "./__python_types__"; 6 | import type {G1Element} from "bls-signatures"; 7 | 8 | export function to_hexstr(r: Uint8Array) { 9 | return (new Word32Array(r)).toString(); 10 | } 11 | 12 | /** 13 | * Get python's bytes.__repr__ style string. 14 | * @see https://github.com/python/cpython/blob/main/Objects/bytesobject.c#L1337 15 | * @param {Uint8Array} r - byteArray to stringify 16 | */ 17 | export function PyBytes_Repr(r: Uint8Array) { 18 | let squotes = 0; 19 | let dquotes = 0; 20 | for(let i=0;i= 0x7f){ 51 | s += "\\x"; 52 | s += b.toString(16).padStart(2, "0"); 53 | } 54 | else{ 55 | s += c; 56 | } 57 | } 58 | 59 | s += quote; 60 | 61 | return s; 62 | } 63 | 64 | export type BytesFromType = "hex"|"utf8"|"G1Element"; 65 | 66 | /** 67 | * Unlike python, there is no immutable byte type in javascript. 68 | */ 69 | export class Bytes { 70 | private readonly _b: Uint8Array; 71 | public static readonly NULL = new Bytes(); 72 | 73 | public constructor(value?: Uint8Array|Bytes|None) { 74 | if(value instanceof Uint8Array){ 75 | this._b = value; 76 | } 77 | else if(isBytes(value)){ 78 | this._b = value.raw(); 79 | } 80 | else if(!value || value === None){ 81 | this._b = new Uint8Array(); 82 | } 83 | else{ 84 | throw new Error(`Invalid value: ${JSON.stringify(value)}`); 85 | } 86 | } 87 | 88 | public static from(value?: Uint8Array|Bytes|number[]|string|G1Element|None, type?: BytesFromType){ 89 | if(value === None || value === null){ 90 | return new Bytes(value); 91 | } 92 | else if(value instanceof Uint8Array){ 93 | return new Bytes(value.slice()); 94 | } 95 | else if(isBytes(value)){ 96 | return new Bytes(value.data()); 97 | } 98 | else if(Array.isArray(value) && value.every(v => typeof v === "number")){ 99 | if(value.some(v => (v < 0 || v > 255))){ 100 | throw new Error("Bytes must be in range [0, 256)"); 101 | } 102 | return new Bytes(Uint8Array.from(value)); 103 | } 104 | else if(typeof value === "string"){ 105 | if(!value){ 106 | return new Bytes(); 107 | } 108 | if(type === "hex"){ 109 | value = value.replace(/^0x/, ""); 110 | return new Bytes(Hex.parse(value).toUint8Array()); 111 | } 112 | else /* if(type === "utf8") */ { 113 | return new Bytes(Utf8.parse(value).toUint8Array()); 114 | } 115 | } 116 | else if(type === "G1Element"){ 117 | if(typeof (value as G1Element).serialize !== "function"){ 118 | throw new Error("Invalid G1Element"); 119 | } 120 | const uint8array = (value as G1Element).serialize(); 121 | return new Bytes(uint8array); 122 | } 123 | 124 | throw new Error(`Invalid value: ${JSON.stringify(value)}`); 125 | } 126 | 127 | public static SHA256(value: string|Bytes|Uint8Array){ 128 | let w; 129 | if(typeof value === "string"){ 130 | w = SHA256.hash(value); 131 | } 132 | else if(value instanceof Uint8Array){ 133 | w = new Word32Array(value); 134 | w = SHA256.hash(w); 135 | } 136 | else if(isBytes(value)){ 137 | w = value.as_word(); 138 | w = SHA256.hash(w); 139 | } 140 | else{ 141 | throw new Error("Invalid argument"); 142 | } 143 | 144 | return new Bytes(w.toUint8Array()); 145 | } 146 | 147 | public get length(){ 148 | return this._b.length; 149 | } 150 | 151 | public at(i: number){ 152 | return this._b[i] | 0; 153 | } 154 | 155 | public concat(b: Bytes){ 156 | const thisBin = this._b; 157 | const thatBin = b.raw(); 158 | const concatBin = new Uint8Array(thisBin.length + thatBin.length); 159 | concatBin.set(thisBin, 0); 160 | concatBin.set(thatBin, thisBin.length); 161 | return new Bytes(concatBin); 162 | } 163 | 164 | public repeat(n: number){ 165 | const ret = new Uint8Array(this.length*n); 166 | for(let i=0;i 0 ? 1 : -1; 246 | } 247 | const self_raw_byte = this._b; 248 | const self_byteLength = self_raw_byte.byteLength; 249 | const dv_self = new DataView(self_raw_byte.buffer, self_raw_byte.byteOffset, self_byteLength); 250 | const other_raw_byte = other.raw(); 251 | const other_byteLength = other_raw_byte.byteLength; 252 | const dv_other = new DataView(other_raw_byte.buffer, other_raw_byte.byteOffset, other_byteLength); 253 | 254 | // const minByteLength = Math.min(self_byteLength, other_byteLength); 255 | const minByteLength = Math.min(self_byteLength, other_byteLength) - 4; 256 | const ui32MaxCount = (Math.max(self_byteLength, other_byteLength) / 4) | 0; 257 | let offset = 0; 258 | for(offset=0;offset minByteLength){ // k > minByteLength - 4 ==(optimize)==> minByteLength = minByteLength - 4 261 | if(k > minByteLength){ 262 | break; 263 | } 264 | const ui32_self = dv_self.getUint32(k); 265 | const ui32_other = dv_other.getUint32(k); 266 | if(ui32_self !== ui32_other){ 267 | return ui32_self > ui32_other ? 1 : -1; 268 | } 269 | } 270 | 271 | offset = offset*4; 272 | const ui8MaxCount = Math.max(self_byteLength, other_byteLength); 273 | for(let i=offset;i self_byteLength){ 276 | return -1; 277 | } 278 | else if(k > other_byteLength){ 279 | return 1; 280 | } 281 | const ui8_self = dv_self.getUint8(i); 282 | const ui8_other = dv_other.getUint8(i); 283 | if(ui8_self !== ui8_other){ 284 | return ui8_self > ui8_other ? 1 : -1; 285 | } 286 | } 287 | 288 | return 0; 289 | } 290 | 291 | public toJSON(){ 292 | return this.hex(); 293 | } 294 | } 295 | 296 | export function b(utf8Str: string, type:"utf8"|"hex" = "utf8"){ 297 | return Bytes.from(utf8Str, type); 298 | } 299 | 300 | export function h(hexStr: string){ 301 | return Bytes.from(hexStr, "hex"); 302 | } 303 | 304 | export function list(iterable: Iterable){ 305 | const arr: T[] = []; 306 | for(const item of iterable){ 307 | arr.push(item); 308 | } 309 | return arr; 310 | } 311 | 312 | export function str(x: any){ 313 | if(typeof x.toString === "function"){ 314 | return x.toString(); 315 | } 316 | return `${x}`; 317 | } 318 | 319 | export function repr(x: any){ 320 | if(typeof x.__repr__ === "function"){ 321 | return x.__repr__(); 322 | } 323 | return str(x); 324 | } 325 | 326 | export class Tuple extends Array { 327 | public constructor(...items: [T1, T2]) { 328 | super(...items); 329 | Object.freeze(this); 330 | return this; 331 | } 332 | 333 | public toString(){ 334 | return `(${this[0]}, ${this[1]})`; 335 | } 336 | } 337 | 338 | export function t(v1: T1, v2: T2){ 339 | return new Tuple(v1, v2); 340 | } 341 | 342 | export function isTuple(v: unknown): v is Tuple { 343 | return v instanceof Array && Object.isFrozen(v) && v.length === 2; 344 | } 345 | 346 | /** 347 | * Check whether an argument is a list and not a tuple 348 | */ 349 | export function isList(v: unknown): v is unknown[] { 350 | return Array.isArray(v) && !isTuple(v); 351 | } 352 | 353 | export function isIterable(v: any): v is unknown[] { 354 | if(Array.isArray(v)){ // Including Tuple. 355 | return true; 356 | } 357 | else if(typeof v === "string"){ 358 | return false; 359 | } 360 | else if(typeof v[Symbol.iterator] === "function"){ 361 | return true; 362 | } 363 | return false; 364 | } 365 | 366 | export function isBytes(v: any): v is Bytes { 367 | return v && typeof v.length === "number" 368 | && typeof v.at === "function" 369 | && typeof v.raw === "function" 370 | && typeof v.data === "function" 371 | && typeof v.hex === "function" 372 | && typeof v.decode === "function" 373 | && typeof v.equal_to === "function" 374 | && typeof v.compare === "function" 375 | ; 376 | } 377 | 378 | export class Stream { 379 | public static readonly INITIAL_BUFFER_SIZE = 64*1024; 380 | private _seek: number; 381 | private _length: number; 382 | private _buffer: Uint8Array; 383 | private _bufAllocMultiplier = 4; 384 | 385 | public constructor(b?: Bytes) { 386 | this._seek = 0; 387 | 388 | if(b){ 389 | if(b.length > Stream.INITIAL_BUFFER_SIZE){ 390 | this._buffer = new Uint8Array(b.length*2); 391 | } 392 | else{ 393 | this._buffer = new Uint8Array(Stream.INITIAL_BUFFER_SIZE); 394 | } 395 | 396 | this._buffer.set(b.raw()); 397 | this._length = b.length; 398 | } 399 | else{ 400 | this._buffer = new Uint8Array(Stream.INITIAL_BUFFER_SIZE); 401 | this._length = 0; 402 | } 403 | } 404 | 405 | public get seek(){ 406 | return this._seek; 407 | } 408 | 409 | public set seek(value){ 410 | if(value < 0){ 411 | this._seek = this.length - 1; 412 | } 413 | else if(value > this.length - 1){ 414 | this._seek = this.length; 415 | } 416 | else{ 417 | this._seek = value; 418 | } 419 | } 420 | 421 | public get length(){ 422 | return this._length; 423 | } 424 | 425 | protected reAllocate(size?: number){ 426 | let s = typeof size === "number" ? size : this._buffer.length * this._bufAllocMultiplier; 427 | /** 428 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length 429 | */ 430 | if(s > 4294967295){ // 4294967295 = 2**32 - 1 431 | s = 4294967295; 432 | } 433 | const buf = new Uint8Array(s); 434 | buf.set(this._buffer); 435 | this._buffer = buf; 436 | } 437 | 438 | public write(b: Bytes){ 439 | const newLength = Math.max(this.length, b.length + this._seek); 440 | if(newLength > this._buffer.length){ 441 | this.reAllocate(newLength * this._bufAllocMultiplier); 442 | } 443 | 444 | const offset = this.seek; 445 | this._buffer.set(b.raw(), offset); 446 | 447 | this._length = newLength; 448 | this.seek += b.length; // Don't move this line prior to `this._length = newLength`! 449 | return b.length; 450 | } 451 | 452 | public read(size: number): Bytes { 453 | if(this.seek > this.length-1){ 454 | return new Bytes(); // Return empty byte 455 | } 456 | 457 | if(this.seek + size <= this.length){ 458 | const u8 = this._buffer.subarray(this.seek, this.seek + size); 459 | this.seek += size; 460 | return new Bytes(u8); 461 | } 462 | 463 | const u8 = this._buffer.subarray(this.seek, this.length); 464 | this.seek += size; 465 | return new Bytes(u8); 466 | } 467 | 468 | public getValue(): Bytes { 469 | return new Bytes(this.asUint8Array()); 470 | } 471 | 472 | public asUint8Array(): Uint8Array { 473 | return this._buffer.subarray(0, this.length); 474 | } 475 | } 476 | 477 | /** 478 | * Python's style division. 479 | * In javascript, `-8 / 5 === -1` while `-8 / 5 == -2` in Python 480 | */ 481 | export function division(a: bigint, b: bigint): bigint { 482 | if(a === BigInt(0)){ 483 | return a; 484 | } 485 | else if(b === BigInt(0)){ 486 | throw new Error("Division by zero!"); 487 | } 488 | else if(a > BigInt(0) && b > BigInt(0) && a < b){ 489 | return BigInt(0); 490 | } 491 | else if(a < BigInt(0) && b < BigInt(0) && a > b){ 492 | return BigInt(0); 493 | } 494 | 495 | const div = a / b; 496 | if(a === div*b){ 497 | return div; 498 | } 499 | else if(div > BigInt(0)){ 500 | return div; 501 | } 502 | return div - BigInt(1); 503 | } 504 | 505 | /** 506 | * Python's style modulo. 507 | * In javascript, `-8 % 5 === -3` while `-8 % 5 == 2` in Python 508 | */ 509 | export function modulo(a: bigint, b: bigint): bigint { 510 | const div = division(a, b); 511 | return a - b*div; 512 | } 513 | 514 | export function divmod(a: bigint, b: bigint): Tuple { 515 | const div = division(a, b); 516 | return t(div, a - b*div); 517 | } 518 | -------------------------------------------------------------------------------- /src/as_javascript.ts: -------------------------------------------------------------------------------- 1 | import {CastableType, SExp} from "./SExp"; 2 | import {Bytes, Tuple, t, isList, isBytes} from "./__type_compatibility__"; 3 | 4 | export type TOpStack = Array<(op_stack: TOpStack, val_stack: TValStack) => unknown>; 5 | export type TValStack = Array>; 6 | export type TToSexpF = (arg: CastableType) => SExp; 7 | export type TToJavascript = Bytes | Bytes[] | Tuple | TToJavascript[]; 8 | 9 | 10 | export function as_javascript(sexp: SExp){ 11 | function _roll(op_stack: TOpStack, val_stack: TValStack){ 12 | const v1 = val_stack.pop() as SExp; 13 | const v2 = val_stack.pop() as SExp; 14 | val_stack.push(v1); 15 | val_stack.push(v2); 16 | } 17 | 18 | function _make_tuple(op_stack: TOpStack, val_stack: TValStack){ 19 | const left = val_stack.pop() as SExp; 20 | const right = val_stack.pop() as SExp; 21 | if(isBytes(right) && right.equal_to(Bytes.NULL)){ 22 | val_stack.push([left]); 23 | } 24 | else if(isList(right)){ 25 | const v = [left].concat(right); 26 | val_stack.push(v); 27 | } 28 | else{ 29 | val_stack.push(t(left, right)); 30 | } 31 | } 32 | 33 | function _as_javascript(op_stack: TOpStack, val_stack: TValStack){ 34 | const v = val_stack.pop() as SExp; 35 | const pair = v.as_pair(); 36 | if(pair){ 37 | const [left, right] = pair; 38 | op_stack.push(_make_tuple); 39 | op_stack.push(_as_javascript); 40 | op_stack.push(_roll); 41 | op_stack.push(_as_javascript); 42 | val_stack.push(left); 43 | val_stack.push(right); 44 | } 45 | else{ 46 | val_stack.push(v.atom as Bytes); 47 | } 48 | } 49 | 50 | const op_stack: TOpStack = [_as_javascript]; 51 | const val_stack = [sexp]; 52 | while(op_stack.length){ 53 | const op_f = op_stack.pop(); 54 | if(op_f){ 55 | op_f(op_stack, val_stack); 56 | } 57 | } 58 | 59 | return (val_stack[val_stack.length-1] as unknown) as TToJavascript; 60 | } 61 | -------------------------------------------------------------------------------- /src/casts.ts: -------------------------------------------------------------------------------- 1 | import {None} from "./__python_types__"; 2 | import {Bytes} from "./__type_compatibility__"; 3 | 4 | export type TConvertOption = { 5 | signed: boolean; 6 | }; 7 | 8 | export function int_from_bytes(b: Bytes|None, option?: Partial): number { 9 | if(!b || b.length === 0){ 10 | return 0; 11 | } 12 | else if(b.length*8 > 52){ 13 | throw new Error("Cannot convert Bytes to Integer larger than 52bit. Use bigint_from_bytes instead."); 14 | } 15 | const signed = (option && typeof option.signed === "boolean") ? option.signed : false; 16 | let unsigned32 = 0; 17 | const ui8array = b.raw(); 18 | const dv = new DataView(ui8array.buffer, ui8array.byteOffset, ui8array.byteLength); 19 | const bytes4Remain = dv.byteLength % 4; 20 | const bytes4Length = (dv.byteLength - bytes4Remain) / 4; 21 | 22 | let order = 1; 23 | for(let i=bytes4Length-1;i>=0;i--){ 24 | const byte32 = dv.getUint32(i*4 + bytes4Remain); 25 | unsigned32 += byte32 * order; 26 | order = Number(BigInt(order) << BigInt(32)); 27 | } 28 | 29 | if(bytes4Remain > 0){ 30 | if(bytes4Length === 0){ 31 | order = 1; 32 | } 33 | for(let i=bytes4Remain-1;i>=0;i--){ 34 | const byte = ui8array[i]; 35 | unsigned32 += byte * order; 36 | order = Number(BigInt(order) << BigInt(8)); 37 | } 38 | } 39 | 40 | // If the first bit is 1, it is recognized as a negative number. 41 | if(signed && (ui8array[0] & 0x80)){ 42 | return unsigned32 - Number(BigInt(1) << BigInt(b.length*8)); 43 | } 44 | return unsigned32; 45 | } 46 | 47 | export function bigint_from_bytes(b: Bytes|None, option?: Partial): bigint { 48 | if(!b || b.length === 0){ 49 | return BigInt(0); 50 | } 51 | const signed = (option && typeof option.signed === "boolean") ? option.signed : false; 52 | let unsigned32 = BigInt(0); 53 | const ui8array = b.raw(); 54 | const dv = new DataView(ui8array.buffer, ui8array.byteOffset, ui8array.byteLength); 55 | const bytes4Remain = dv.byteLength % 4; 56 | const bytes4Length = (dv.byteLength - bytes4Remain) / 4; 57 | 58 | let order = BigInt(1); 59 | for(let i=bytes4Length-1;i>=0;i--){ 60 | const byte32 = dv.getUint32(i*4 + bytes4Remain); 61 | unsigned32 += BigInt(byte32) * order; 62 | order <<= BigInt(32); 63 | } 64 | 65 | if(bytes4Remain > 0){ 66 | if(bytes4Length === 0){ 67 | order = BigInt(1); 68 | } 69 | for(let i=bytes4Remain-1;i>=0;i--){ 70 | const byte = ui8array[i]; 71 | unsigned32 += BigInt(byte) * order; 72 | order <<= BigInt(8); 73 | } 74 | } 75 | 76 | // If the first bit is 1, it is recognized as a negative number. 77 | if(signed && (ui8array[0] & 0x80)){ 78 | return unsigned32 - (BigInt(1) << BigInt(b.length*8)); 79 | } 80 | return unsigned32; 81 | } 82 | 83 | export function int_to_bytes(v: number, option?: Partial): Bytes { 84 | if(v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER){ 85 | throw new Error(`The int value is beyond ${v > 0 ? "MAX_SAFE_INTEGER" : "MIN_SAFE_INTEGER"}: ${v}`); 86 | } 87 | if(v === 0){ 88 | return Bytes.NULL; 89 | } 90 | 91 | const signed = (option && typeof option.signed === "boolean") ? option.signed : false; 92 | if(!signed && v < 0){ 93 | throw new Error("OverflowError: can't convert negative int to unsigned"); 94 | } 95 | 96 | let byte_count = 1; 97 | const div = signed ? 1 : 0; 98 | const b16 = 65536; 99 | if(v > 0){ 100 | let right_hand = (v + 1) * (div + 1); 101 | while((b16 ** ((byte_count-1)/2 + 1)) < right_hand){ 102 | byte_count += 2; 103 | } 104 | right_hand = (v + 1) * (div + 1); 105 | while (2 ** (8 * byte_count) < right_hand) { 106 | byte_count++; 107 | } 108 | } 109 | else if(v < 0){ 110 | let right_hand = (-v + 1) * (div + 1); 111 | while((b16 ** ((byte_count-1)/2 + 1)) < right_hand){ 112 | byte_count += 2; 113 | } 114 | right_hand = -v * 2; 115 | while (2 ** (8 * byte_count) < right_hand) { 116 | byte_count++; 117 | } 118 | } 119 | 120 | const extraByte = signed && v > 0 && ((v >> ((byte_count-1)*8)) & 0x80) > 0 ? 1 : 0; 121 | const u8 = new Uint8Array(byte_count + extraByte); 122 | for(let i=0;i> (byte_count-i-1)*8) & 0xff; 125 | } 126 | 127 | return new Bytes(u8); 128 | } 129 | 130 | // The reason to use `pow` instead of `**` is that some transpiler automatically converts `**` into `Math.pow` 131 | // which cannot be used against bigint. 132 | export function pow(base: bigint, exp: bigint): bigint { 133 | return base ** exp; 134 | // The code below was once tested, but it is 100x slower than '**' operator. 135 | // So I gave up to use it. 136 | /* 137 | if(exp === BigInt(0)){ 138 | return BigInt(1); 139 | } 140 | else if(exp === BigInt(1)){ 141 | return base; 142 | } 143 | else if(exp < BigInt(0)){ 144 | throw new RangeError("BigInt negative exponent"); 145 | } 146 | 147 | const stack: Array<[bigint, bigint]> = []; 148 | stack.push([base, exp]); 149 | let retVal: bigint = BigInt(1); 150 | while(stack.length){ 151 | [base, exp] = stack.pop() as [bigint, bigint]; 152 | if(exp === BigInt(0)){ 153 | continue; 154 | } 155 | else if(exp === BigInt(1)){ 156 | retVal *= base; 157 | continue; 158 | } 159 | 160 | if(exp % BigInt(2)){ 161 | stack.push([base*base, exp/BigInt(2)]); 162 | stack.push([base, BigInt(1)]); 163 | } 164 | else{ 165 | stack.push([base*base, exp/BigInt(2)]); 166 | } 167 | } 168 | 169 | return retVal; 170 | */ 171 | } 172 | 173 | export function bigint_to_bytes(v: bigint, option?: Partial): Bytes { 174 | if(v === BigInt(0)){ 175 | return Bytes.NULL; 176 | } 177 | 178 | const signed = (option && typeof option.signed === "boolean") ? option.signed : false; 179 | if(!signed && v < BigInt(0)){ 180 | throw new Error("OverflowError: can't convert negative int to unsigned"); 181 | } 182 | let byte_count = 1; 183 | const div = BigInt(signed ? 1 : 0); 184 | const b32 = BigInt(4294967296); 185 | if(v > 0){ 186 | let right_hand = (v + BigInt(1)) * (div + BigInt(1)); 187 | while(pow(b32, BigInt((byte_count-1)/4 + 1)) < right_hand){ 188 | byte_count += 4; 189 | } 190 | right_hand = (v + BigInt(1)) * (div + BigInt(1)); 191 | while(pow(BigInt(2), (BigInt(8) * BigInt(byte_count))) < right_hand) { 192 | byte_count++; 193 | } 194 | } 195 | else if(v < 0){ 196 | let right_hand = (-v + BigInt(1)) * (div + BigInt(1)); 197 | while(pow(b32, BigInt((byte_count-1)/4 + 1)) < right_hand){ 198 | byte_count += 4; 199 | } 200 | right_hand = -v * BigInt(2); 201 | while(pow(BigInt(2), (BigInt(8) * BigInt(byte_count))) < right_hand) { 202 | byte_count++; 203 | } 204 | } 205 | 206 | const extraByte = (signed && v > 0 && ((v >> (BigInt(byte_count-1)*BigInt(8))) & BigInt(0x80)) > BigInt(0)) ? 1 : 0; 207 | const total_bytes = byte_count + extraByte; 208 | const u8 = new Uint8Array(total_bytes); 209 | const dv = new DataView(u8.buffer); 210 | const byte4Remain = byte_count % 4; 211 | const byte4Length = (byte_count - byte4Remain) / 4; 212 | 213 | let bitmask = BigInt(0xffffffff); 214 | for(let i=0;i> BigInt(32)*BigInt(i)) & bitmask); 216 | const pointer = extraByte + byte4Remain + (byte4Length-1 - i)*4; 217 | dv.setUint32(pointer, num); 218 | } 219 | v >>= BigInt(32) * BigInt(byte4Length); 220 | bitmask = BigInt(0xff); 221 | for(let i=0;i> BigInt(8)*BigInt(i)) & bitmask); 223 | const pointer = extraByte + byte4Remain-1-i; 224 | dv.setUint8(pointer, num); 225 | } 226 | 227 | return new Bytes(u8); 228 | } 229 | 230 | /** 231 | * Return the number of bytes required to represent this integer. 232 | * @param {number} v 233 | */ 234 | export function limbs_for_int(v: number|bigint): number { 235 | if(v === 0 || v === BigInt(0)){ 236 | return 0; 237 | } 238 | return ((v >= 0 ? v : -v).toString(2).length + 7) >> 3; 239 | } 240 | -------------------------------------------------------------------------------- /src/core_ops.ts: -------------------------------------------------------------------------------- 1 | import {SExp} from "./SExp"; 2 | import {Bytes, t} from "./__type_compatibility__"; 3 | import {CONS_COST, EQ_BASE_COST, EQ_COST_PER_BYTE, FIRST_COST, IF_COST, LISTP_COST, REST_COST} from "./costs"; 4 | import {EvalError} from "./EvalError"; 5 | 6 | export function op_if(args: SExp){ 7 | if(args.list_len() !== 3){ 8 | throw new EvalError("i takes exactly 3 arguments", args); 9 | } 10 | const r = args.rest(); 11 | if(args.first().nullp()){ 12 | return t(IF_COST, r.rest().first()); 13 | } 14 | return t(IF_COST, r.first()); 15 | } 16 | 17 | export function op_cons(args: SExp){ 18 | if(args.list_len() !== 2){ 19 | throw new EvalError("c takes exactly 2 arguments", args); 20 | } 21 | return t(CONS_COST, args.first().cons(args.rest().first())); 22 | } 23 | 24 | export function op_first(args: SExp){ 25 | if(args.list_len() !== 1){ 26 | throw new EvalError("f takes exactly 1 argument", args); 27 | } 28 | return t(FIRST_COST, args.first().first()); 29 | } 30 | 31 | export function op_rest(args: SExp){ 32 | if(args.list_len() !== 1){ 33 | throw new EvalError("r takes exactly 1 argument", args); 34 | } 35 | return t(REST_COST, args.first().rest()); 36 | } 37 | 38 | export function op_listp(args: SExp){ 39 | if(args.list_len() !== 1){ 40 | throw new EvalError("l takes exactly 1 argument", args); 41 | } 42 | return t(LISTP_COST, args.first().listp() ? SExp.TRUE : SExp.FALSE); 43 | } 44 | 45 | export function op_raise(args: SExp){ 46 | if(args.list_len() === 1 && !args.first().listp()){ 47 | throw new EvalError("clvm raise", args.first()); 48 | } 49 | else{ 50 | throw new EvalError("clvm raise", args); 51 | } 52 | } 53 | 54 | export function op_eq(args: SExp){ 55 | if(args.list_len() !== 2){ 56 | throw new EvalError("= takes exactly 2 arguments", args); 57 | } 58 | const a0 = args.first(); 59 | const a1 = args.rest().first(); 60 | if(a0.pair || a1.pair){ 61 | throw new EvalError("= on list", a0.pair ? a0 : a1); 62 | } 63 | 64 | const b0 = a0.atom as Bytes; 65 | const b1 = a1.atom as Bytes; 66 | let cost = EQ_BASE_COST; 67 | cost += (b0.length + b1.length) * EQ_COST_PER_BYTE; 68 | return t(cost, b0.equal_to(b1) ? SExp.TRUE : SExp.FALSE); 69 | } 70 | -------------------------------------------------------------------------------- /src/costs.ts: -------------------------------------------------------------------------------- 1 | export const IF_COST = 33; 2 | export const CONS_COST = 50; 3 | export const FIRST_COST = 30; 4 | export const REST_COST = 30; 5 | export const LISTP_COST = 19; 6 | 7 | export const MALLOC_COST_PER_BYTE = 10; 8 | 9 | export const ARITH_BASE_COST = 99; 10 | export const ARITH_COST_PER_BYTE = 3; 11 | export const ARITH_COST_PER_ARG = 320; 12 | 13 | export const LOG_BASE_COST = 100; 14 | export const LOG_COST_PER_BYTE = 3; 15 | export const LOG_COST_PER_ARG = 264; 16 | 17 | export const GRS_BASE_COST = 117; 18 | export const GRS_COST_PER_BYTE = 1; 19 | 20 | export const EQ_BASE_COST = 117; 21 | export const EQ_COST_PER_BYTE = 1; 22 | 23 | export const GR_BASE_COST = 498; 24 | export const GR_COST_PER_BYTE = 2; 25 | 26 | export const DIVMOD_BASE_COST = 1116; 27 | export const DIVMOD_COST_PER_BYTE = 6; 28 | 29 | export const DIV_BASE_COST = 988; 30 | export const DIV_COST_PER_BYTE = 4; 31 | 32 | export const SHA256_BASE_COST = 87; 33 | export const SHA256_COST_PER_ARG = 134; 34 | export const SHA256_COST_PER_BYTE = 2; 35 | 36 | export const POINT_ADD_BASE_COST = 101094; 37 | export const POINT_ADD_COST_PER_ARG = 1343980; 38 | 39 | export const PUBKEY_BASE_COST = 1325730; 40 | export const PUBKEY_COST_PER_BYTE = 38; 41 | 42 | export const MUL_BASE_COST = 92; 43 | export const MUL_COST_PER_OP = 885; 44 | export const MUL_LINEAR_COST_PER_BYTE = 6; 45 | export const MUL_SQUARE_COST_PER_BYTE_DIVIDER = 128; 46 | 47 | export const STRLEN_BASE_COST = 173; 48 | export const STRLEN_COST_PER_BYTE = 1; 49 | 50 | export const PATH_LOOKUP_BASE_COST = 40; 51 | export const PATH_LOOKUP_COST_PER_LEG = 4; 52 | export const PATH_LOOKUP_COST_PER_ZERO_BYTE = 4; 53 | 54 | export const CONCAT_BASE_COST = 142; 55 | export const CONCAT_COST_PER_ARG = 135; 56 | export const CONCAT_COST_PER_BYTE = 3; 57 | 58 | export const BOOL_BASE_COST = 200; 59 | export const BOOL_COST_PER_ARG = 300; 60 | 61 | export const ASHIFT_BASE_COST = 596; 62 | export const ASHIFT_COST_PER_BYTE = 3; 63 | 64 | export const LSHIFT_BASE_COST = 277; 65 | export const LSHIFT_COST_PER_BYTE = 3; 66 | 67 | export const LOGNOT_BASE_COST = 331; 68 | export const LOGNOT_COST_PER_BYTE = 3; 69 | 70 | export const APPLY_COST = 90; 71 | export const QUOTE_COST = 20; 72 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {SExp} from "./SExp"; 2 | 3 | export * from "./__debug__"; 4 | export * from "./__bls_signatures__"; 5 | export * from "./__clvm_wasm__"; 6 | export * from "./__python_types__"; 7 | export * from "./__type_compatibility__"; 8 | export * from "./as_javascript"; 9 | export * from "./casts"; 10 | export * from "./CLVMObject"; 11 | export * from "./core_ops"; 12 | export * from "./costs"; 13 | export * from "./EvalError"; 14 | export * from "./initialize"; 15 | export * from "./more_ops"; 16 | export * from "./op_utils"; 17 | export * from "./operators"; 18 | export * from "./run_program"; 19 | export * from "./serialize"; 20 | export * from "./SExp"; 21 | 22 | export const to_sexp_f = SExp.to; 23 | -------------------------------------------------------------------------------- /src/initialize.ts: -------------------------------------------------------------------------------- 1 | import {initializeBLS} from "./__bls_signatures__"; 2 | import {initializeClvmWasm} from "./__clvm_wasm__"; 3 | 4 | /** 5 | * Always call and wait this async function to be finished to initialize async bls module loading. 6 | */ 7 | export async function initialize(){ 8 | await Promise.all([initializeBLS(), initializeClvmWasm()]); 9 | } 10 | -------------------------------------------------------------------------------- /src/op_utils.ts: -------------------------------------------------------------------------------- 1 | import {SExp} from "./SExp"; 2 | 3 | export function operators_for_dict( 4 | keyword_to_atom: Record, 5 | op_dict: Record, 6 | op_name_lookup?: Record, 7 | ){ 8 | if (!op_name_lookup){ 9 | op_name_lookup = {}; 10 | } 11 | const d: Record unknown> = {}; 12 | for(const op of Object.keys(keyword_to_atom)){ 13 | const op_name = `op_${op_name_lookup[op] || op}`; 14 | const op_f = op_dict[op_name] as (args: SExp) => unknown; 15 | if(typeof op_f === "function"){ 16 | d[keyword_to_atom[op]] = op_f; 17 | } 18 | } 19 | return d; 20 | } 21 | 22 | export function operators_for_module( 23 | keyword_to_atom: Record, 24 | mod: Record, 25 | op_name_lookup?: Record, 26 | ){ 27 | if (!op_name_lookup){ 28 | op_name_lookup = {}; 29 | } 30 | return operators_for_dict(keyword_to_atom, mod, op_name_lookup); 31 | } -------------------------------------------------------------------------------- /src/operators.ts: -------------------------------------------------------------------------------- 1 | import {int_from_bytes} from "./casts"; 2 | import {SExp} from "./SExp"; 3 | import {Bytes, Tuple, t, isBytes} from "./__type_compatibility__"; 4 | import type {CLVMType} from "./CLVMObject"; 5 | import {EvalError} from "./EvalError"; 6 | import { 7 | ARITH_BASE_COST, 8 | ARITH_COST_PER_ARG, 9 | ARITH_COST_PER_BYTE, 10 | CONCAT_BASE_COST, 11 | CONCAT_COST_PER_ARG, 12 | CONCAT_COST_PER_BYTE, 13 | MUL_BASE_COST, 14 | MUL_COST_PER_OP, 15 | MUL_LINEAR_COST_PER_BYTE, 16 | MUL_SQUARE_COST_PER_BYTE_DIVIDER, 17 | } from "./costs"; 18 | import {operators_for_module} from "./op_utils"; 19 | import * as core_ops from "./core_ops"; 20 | import * as more_ops from "./more_ops"; 21 | 22 | /* 23 | export const KEYWORDS = [ 24 | // core opcodes 0x01-x08 25 | ". q a i c f r l x ", 26 | 27 | // opcodes on atoms as strings 0x09-0x0f 28 | "= >s sha256 substr strlen concat . ", 29 | 30 | // opcodes on atoms as ints 0x10-0x17 31 | "+ - * / divmod > ash lsh ", 32 | 33 | // opcodes on atoms as vectors of bools 0x18-0x1c 34 | "logand logior logxor lognot . ", 35 | 36 | // opcodes for bls 1381 0x1d-0x1f 37 | "point_add pubkey_for_exp . ", 38 | 39 | // bool opcodes 0x20-0x23 40 | "not any all . ", 41 | 42 | // misc 0x24 43 | "softfork ", 44 | ].join("").trim().split(/\s/); 45 | export const KEYWORD_FROM_ATOM = Object 46 | .entries(KEYWORDS) 47 | .reduce>((acc, v) => { 48 | acc[int_to_bytes(+v[0]).toString()] = v[1]; 49 | return acc; 50 | }, {}); 51 | export const KEYWORD_TO_ATOM = Object 52 | .entries(KEYWORD_FROM_ATOM) 53 | .reduce>((acc, v) => { 54 | acc[v[1]] = v[0]; 55 | return acc; 56 | }, {}); 57 | */ 58 | export const KEYWORD_FROM_ATOM = { 59 | "00": ".", 60 | // core opcodes 0x01-x08 61 | "01": "q", 62 | "02": "a", 63 | "03": "i", 64 | "04": "c", 65 | "05": "f", 66 | "06": "r", 67 | "07": "l", 68 | "08": "x", 69 | // opcodes on atoms as strings 0x09-0x0f 70 | "09": "=", 71 | "0a": ">s", 72 | "0b": "sha256", 73 | "0c": "substr", 74 | "0d": "strlen", 75 | "0e": "concat", 76 | "0f": ".", 77 | // opcodes on atoms as ints 0x10-0x17 78 | "10": "+", 79 | "11": "-", 80 | "12": "*", 81 | "13": "/", 82 | "14": "divmod", 83 | "15": ">", 84 | "16": "ash", 85 | "17": "lsh", 86 | // opcodes on atoms as vectors of bools 0x18-0x1c 87 | "18": "logand", 88 | "19": "logior", 89 | "1a": "logxor", 90 | "1b": "lognot", 91 | "1c": ".", 92 | // opcodes for bls 1381 0x1d-0x1f 93 | "1d": "point_add", 94 | "1e": "pubkey_for_exp", 95 | "1f": ".", 96 | // bool opcodes 0x20-0x23 97 | "20": "not", 98 | "21": "any", 99 | "22": "all", 100 | "23": ".", 101 | // misc 0x24 102 | "24": "softfork", 103 | }; 104 | export const KEYWORD_TO_ATOM = { 105 | // ".": "00", 106 | // core opcodes 0x01-x08 107 | "q": "01", 108 | "a": "02", 109 | "i": "03", 110 | "c": "04", 111 | "f": "05", 112 | "r": "06", 113 | "l": "07", 114 | "x": "08", 115 | // opcodes on atoms as strings 0x09-0x0f 116 | "=": "09", 117 | ">s": "0a", 118 | "sha256": "0b", 119 | "substr": "0c", 120 | "strlen": "0d", 121 | "concat": "0e", 122 | // ".": "0f", 123 | // opcodes on atoms as ints 0x10-0x17 124 | "+": "10", 125 | "-": "11", 126 | "*": "12", 127 | "/": "13", 128 | "divmod": "14", 129 | ">": "15", 130 | "ash": "16", 131 | "lsh": "17", 132 | // opcodes on atoms as vectors of bools 0x18-0x1c 133 | "logand": "18", 134 | "logior": "19", 135 | "logxor": "1a", 136 | "lognot": "1b", 137 | // ".": "1c", 138 | // opcodes for bls 1381 0x1d-0x1f 139 | "point_add": "1d", 140 | "pubkey_for_exp": "1e", 141 | // ".": "1f", 142 | // bool opcodes 0x20-0x23 143 | "not": "20", 144 | "any": "21", 145 | "all": "22", 146 | ".": "23", 147 | // misc 0x24 148 | "softfork": "24", 149 | }; 150 | export const OP_REWRITE = { 151 | "+": "add", 152 | "-": "subtract", 153 | "*": "multiply", 154 | "/": "div", 155 | "i": "if", 156 | "c": "cons", 157 | "f": "first", 158 | "r": "rest", 159 | "l": "listp", 160 | "x": "raise", 161 | "=": "eq", 162 | ">": "gr", 163 | ">s": "gr_bytes", 164 | }; 165 | 166 | export type ATOMS = keyof typeof KEYWORD_FROM_ATOM; 167 | export type KEYWORDS = keyof typeof KEYWORD_TO_ATOM; 168 | 169 | export function* args_len(op_name: string, args: SExp){ 170 | for(const arg of args.as_iter()){ 171 | if(arg.pair){ 172 | throw new EvalError(`${op_name} requires int args`, arg); 173 | } 174 | yield (arg.atom as Bytes).length; 175 | } 176 | } 177 | 178 | /* 179 | unknown ops are reserved if they start with 0xffff 180 | otherwise, unknown ops are no-ops, but they have costs. The cost is computed 181 | like this: 182 | 183 | byte index (reverse): 184 | | 4 | 3 | 2 | 1 | 0 | 185 | +---+---+---+---+------------+ 186 | | multiplier |XX | XXXXXX | 187 | +---+---+---+---+---+--------+ 188 | ^ ^ ^ 189 | | | + 6 bits ignored when computing cost 190 | cost_multiplier | 191 | + 2 bits 192 | cost_function 193 | 194 | 1 is always added to the multiplier before using it to multiply the cost, this 195 | is since cost may not be 0. 196 | 197 | cost_function is 2 bits and defines how cost is computed based on arguments: 198 | 0: constant, cost is 1 * (multiplier + 1) 199 | 1: computed like operator add, multiplied by (multiplier + 1) 200 | 2: computed like operator mul, multiplied by (multiplier + 1) 201 | 3: computed like operator concat, multiplied by (multiplier + 1) 202 | 203 | this means that unknown ops where cost_function is 1, 2, or 3, may still be 204 | fatal errors if the arguments passed are not atoms. 205 | */ 206 | export function default_unknown_op(op: Bytes, args: SExp): Tuple { 207 | // any opcode starting with ffff is reserved (i.e. fatal error) 208 | // opcodes are not allowed to be empty 209 | if(op.length === 0 || op.subarray(0, 2).equal_to(Bytes.from("0xffff", "hex"))){ 210 | throw new EvalError("reserved operator", SExp.to(op)); 211 | } 212 | 213 | /* 214 | all other unknown opcodes are no-ops 215 | the cost of the no-ops is determined by the opcode number, except the 216 | 6 least significant bits. 217 | */ 218 | const cost_function = (op.at(op.length-1) & 0b11000000) >> 6; 219 | // the multiplier cannot be 0. it starts at 1 220 | 221 | if(op.length > 5){ 222 | throw new EvalError("invalid operator", SExp.to(op)); 223 | } 224 | 225 | // The bytes here is 4bytes or smaller. So `int_from_bytes` is enough. (No bigint_from_bytes required) 226 | const cost_multiplier = int_from_bytes(op.subarray(0, op.length-1), {signed: false}) + 1; 227 | /* 228 | 0 = constant 229 | 1 = like op_add/op_sub 230 | 2 = like op_multiply 231 | 3 = like op_concat 232 | */ 233 | let cost; 234 | if(cost_function === 0){ 235 | cost = 1; 236 | } 237 | else if(cost_function === 1){ 238 | cost = ARITH_BASE_COST; 239 | let arg_size = 0; 240 | for(const length of args_len("unknown op", args)){ 241 | arg_size += length; 242 | cost += ARITH_COST_PER_ARG; 243 | } 244 | cost += arg_size * ARITH_COST_PER_BYTE; 245 | } 246 | else if(cost_function === 2){ 247 | // like op_multiply 248 | cost = MUL_BASE_COST; 249 | const operands = args_len("unknown op", args); 250 | const res = operands.next(); 251 | if(!res.done){ 252 | let vs = res.value; 253 | for(const rs of operands){ 254 | cost += MUL_COST_PER_OP; 255 | cost += (rs + vs) * MUL_LINEAR_COST_PER_BYTE; 256 | cost += ((rs * vs) / MUL_SQUARE_COST_PER_BYTE_DIVIDER) | 0; 257 | vs += rs; 258 | } 259 | } 260 | } 261 | else if(cost_function === 3){ 262 | // like concat 263 | cost = CONCAT_BASE_COST; 264 | let length = 0; 265 | for(const arg of args.as_iter()){ 266 | if(arg.pair){ 267 | throw new EvalError("unknown op on list", arg); 268 | } 269 | cost += CONCAT_COST_PER_ARG; 270 | length += (arg.atom as Bytes).length; 271 | } 272 | cost += length * CONCAT_COST_PER_BYTE; 273 | } 274 | else{ 275 | throw new Error(`Invalid cost_function: ${cost_function}`); 276 | } 277 | 278 | cost *= cost_multiplier; 279 | if(cost >= 2**32){ 280 | throw new EvalError("invalid operator", SExp.to(op)); 281 | } 282 | 283 | return t(cost, SExp.null()); 284 | } 285 | 286 | export const QUOTE_ATOM = Bytes.from(KEYWORD_TO_ATOM["q"], "hex"); 287 | export const APPLY_ATOM = Bytes.from(KEYWORD_TO_ATOM["a"], "hex"); 288 | 289 | type TOpFunc = (args: SExp) => R; 290 | type TBasicAtom = "quote_atom"|"apply_atom"; 291 | type TAtomOpFunctionMap = Record & Partial>; 292 | 293 | function merge(obj1: Record, obj2: Record){ 294 | Object.keys(obj2).forEach(key => { 295 | obj1[key] = obj2[key]; 296 | }); 297 | } 298 | 299 | export type TOperatorDict = { 300 | unknown_op_handler: typeof default_unknown_op; 301 | } 302 | & ((op: Bytes|string|number, args: SExp) => Tuple) 303 | & TAtomOpFunctionMap 304 | & Record 305 | ; 306 | 307 | export type TOperatorDictOption = { 308 | quote_atom: Bytes; 309 | apply_atom: Bytes; 310 | unknown_op_handler: typeof default_unknown_op; 311 | }; 312 | 313 | export function OperatorDict( 314 | atom_op_function_map: TAtomOpFunctionMap|TOperatorDict, 315 | option: Partial = {}, 316 | ): TOperatorDict { 317 | const dict = { 318 | ...atom_op_function_map, 319 | quote_atom: option.quote_atom || (atom_op_function_map as Record).quote_atom, 320 | apply_atom: option.apply_atom || (atom_op_function_map as Record).apply_atom, 321 | unknown_op_handler: option.unknown_op_handler || default_unknown_op, 322 | }; 323 | 324 | if(!dict.quote_atom){ 325 | throw new Error("object has not attribute 'quote_atom'"); 326 | } 327 | else if(!dict.apply_atom){ 328 | throw new Error("object has not attribute 'apply_atom'"); 329 | } 330 | 331 | const OperatorDict = function(op: Bytes|string|number, args: SExp){ 332 | if(typeof op === "string"){ 333 | op = Bytes.from(op, "hex"); 334 | } 335 | else if(typeof op === "number"){ 336 | op = Bytes.from([(op as unknown) as number]); 337 | } 338 | else if(!isBytes(op)){ 339 | throw new Error(`Invalid op: ${JSON.stringify(op)}`); 340 | } 341 | 342 | merge(dict, OperatorDict as any); 343 | 344 | const f = (dict as Record)[op.hex()]; 345 | if(typeof f !== "function"){ 346 | return dict.unknown_op_handler(op, args); 347 | } 348 | else{ 349 | return f(args); 350 | } 351 | }; 352 | 353 | merge(OperatorDict as any, dict); 354 | 355 | return OperatorDict as TOperatorDict; 356 | } 357 | 358 | const _OPERATOR_LOOKUP = OperatorDict( 359 | operators_for_module(KEYWORD_TO_ATOM, core_ops, OP_REWRITE), 360 | { 361 | quote_atom: QUOTE_ATOM, 362 | apply_atom: APPLY_ATOM, 363 | }, 364 | ); 365 | 366 | merge(_OPERATOR_LOOKUP as any, operators_for_module(KEYWORD_TO_ATOM, more_ops, OP_REWRITE)); 367 | 368 | export const OPERATOR_LOOKUP = _OPERATOR_LOOKUP as TOperatorDict; 369 | -------------------------------------------------------------------------------- /src/run_program.ts: -------------------------------------------------------------------------------- 1 | import {None} from "./__python_types__"; 2 | import {SExp} from "./SExp"; 3 | import {TToSexpF} from "./as_javascript"; 4 | import {CLVMType, isAtom, isCons} from "./CLVMObject"; 5 | import {Bytes, Tuple, t} from "./__type_compatibility__"; 6 | import { 7 | APPLY_COST, 8 | PATH_LOOKUP_BASE_COST, 9 | PATH_LOOKUP_COST_PER_LEG, 10 | PATH_LOOKUP_COST_PER_ZERO_BYTE, 11 | QUOTE_COST 12 | } from "./costs"; 13 | import {EvalError} from "./EvalError"; 14 | import {TOperatorDict} from "./operators"; 15 | 16 | export type OpCallable = (opStack: OpStackType, valStack: ValStackType) => number; 17 | export type ValStackType = SExp[]; 18 | export type OpStackType = OpCallable[]; 19 | export type TPreEvalF = (v1: SExp, v2: SExp) => unknown; 20 | 21 | export function to_pre_eval_op(pre_eval_f: TPreEvalF, to_sexp_f: TToSexpF){ 22 | return function my_pre_eval_op(op_stack: OpStackType, value_stack: ValStackType){ 23 | const v = to_sexp_f(value_stack[value_stack.length-1]); 24 | const context = pre_eval_f(v.first(), v.rest()); 25 | if(typeof context === "function"){ 26 | const invoke_context_op = (op_stack: OpStackType, value_stack: ValStackType) => { 27 | context(to_sexp_f(value_stack[value_stack.length-1])); 28 | return 0; 29 | }; 30 | 31 | op_stack.push(invoke_context_op); 32 | } 33 | }; 34 | } 35 | 36 | export function msb_mask(byte: number){ 37 | byte |= byte >> 1; 38 | byte |= byte >> 2; 39 | byte |= byte >> 4; 40 | return (byte + 1) >> 1; 41 | } 42 | 43 | /** 44 | * @deprecated Use `run_chia_program` instead. 45 | */ 46 | export function run_program( 47 | program: SExp, 48 | args: CLVMType, 49 | operator_lookup: TOperatorDict, 50 | max_cost: number|None = None, 51 | pre_eval_f: TPreEvalF|None = None, 52 | ): Tuple{ 53 | console.warn("`run_program is deprecated. Use `run_chia_program` instead.`"); 54 | 55 | program = SExp.to(program); 56 | const pre_eval_op = pre_eval_f ? to_pre_eval_op(pre_eval_f, SExp.to) : None; 57 | 58 | function traverse_path(sexp: SExp, env: SExp): Tuple { 59 | let cost = PATH_LOOKUP_BASE_COST; 60 | cost += PATH_LOOKUP_COST_PER_LEG; 61 | if(sexp.nullp()){ 62 | return t(cost, SExp.null()); 63 | } 64 | 65 | const b = sexp.atom as Bytes; 66 | 67 | let end_byte_cursor = 0; 68 | while(end_byte_cursor < b.length && b.at(end_byte_cursor) === 0){ 69 | end_byte_cursor += 1; 70 | } 71 | 72 | cost += end_byte_cursor * PATH_LOOKUP_COST_PER_ZERO_BYTE; 73 | if(end_byte_cursor === b.length){ 74 | return t(cost, SExp.null()); 75 | } 76 | 77 | // create a bitmask for the most significant *set* bit 78 | // in the last non-zero byte 79 | const end_bitmask = msb_mask(b.at(end_byte_cursor)); 80 | 81 | let byte_cursor = b.length - 1; 82 | let bitmask = 0x01; 83 | while(byte_cursor > end_byte_cursor || bitmask < end_bitmask){ 84 | if(!isCons(env)){ 85 | throw new EvalError("path into atom", env); 86 | } 87 | if(b.at(byte_cursor) & bitmask){ 88 | env = env.rest(); 89 | } 90 | else{ 91 | env = env.first(); 92 | } 93 | cost += PATH_LOOKUP_COST_PER_LEG; 94 | bitmask <<= 1; 95 | if(bitmask === 0x0100){ 96 | byte_cursor -= 1; 97 | bitmask = 0x01; 98 | } 99 | } 100 | return t(cost, env); 101 | } 102 | 103 | function swap_op(op_stack: OpStackType, value_stack: ValStackType): number { 104 | const v2 = value_stack.pop() as SExp; 105 | const v1 = value_stack.pop() as SExp; 106 | value_stack.push(v2); 107 | value_stack.push(v1); 108 | return 0; 109 | } 110 | 111 | function cons_op (op_stack: OpStackType, value_stack: ValStackType): number { 112 | const v1 = value_stack.pop() as SExp; 113 | const v2 = value_stack.pop() as SExp; 114 | value_stack.push(v1.cons(v2)); 115 | return 0; 116 | } 117 | 118 | function eval_op(op_stack: OpStackType, value_stack: ValStackType): number { 119 | if(pre_eval_op){ 120 | pre_eval_op(op_stack, value_stack); 121 | } 122 | 123 | const pair = value_stack.pop() as SExp; 124 | const sexp = pair.first(); 125 | const args = pair.rest(); 126 | 127 | // put a bunch of ops on op_stack 128 | if(!isCons(sexp)){ 129 | // sexp is an atom 130 | const [cost, r] = traverse_path(sexp, args) as [number, SExp]; 131 | value_stack.push(r); 132 | return cost; 133 | } 134 | 135 | const operator = sexp.first(); 136 | if(isCons(operator)){ 137 | const pair = operator.as_pair() as Tuple; 138 | const [new_operator, must_be_nil] = pair; 139 | if(new_operator.pair || !Bytes.NULL.equal_to(must_be_nil.atom)){ 140 | throw new EvalError("in ((X)...) syntax X must be lone atom", sexp); 141 | } 142 | const new_operand_list = sexp.rest(); 143 | value_stack.push(new_operator); 144 | value_stack.push(new_operand_list); 145 | op_stack.push(apply_op); 146 | return APPLY_COST; 147 | } 148 | const op = operator.atom as Bytes; 149 | let operand_list = sexp.rest(); 150 | // op === operator_lookup.quote_atom 151 | if(op.equal_to(operator_lookup.quote_atom)){ 152 | value_stack.push(operand_list); 153 | return QUOTE_COST; 154 | } 155 | 156 | op_stack.push(apply_op); 157 | value_stack.push(operator); 158 | while(!operand_list.nullp()){ 159 | const _ = operand_list.first(); 160 | value_stack.push(_.cons(args)); 161 | op_stack.push(cons_op); 162 | op_stack.push(eval_op); 163 | op_stack.push(swap_op); 164 | operand_list = operand_list.rest(); 165 | } 166 | value_stack.push(SExp.null()); 167 | return 1; 168 | } 169 | 170 | function apply_op(op_stack: OpStackType, value_stack: ValStackType): number { 171 | const operand_list = value_stack.pop() as SExp; 172 | const operator = value_stack.pop() as SExp; 173 | if(!isAtom(operator)){ 174 | throw new EvalError("internal error", operator); 175 | } 176 | 177 | const op = operator.atom; 178 | // op === operator_lookup.apply_atom 179 | if(op.equal_to(operator_lookup.apply_atom)){ 180 | if(operand_list.list_len() !== 2){ 181 | throw new EvalError("apply requires exactly 2 parameters", operand_list); 182 | } 183 | const new_program = operand_list.first(); 184 | const new_args = operand_list.rest().first(); 185 | value_stack.push(new_program.cons(new_args)); 186 | op_stack.push(eval_op); 187 | return APPLY_COST; 188 | } 189 | 190 | const [additional_cost, r] = operator_lookup(op, operand_list) as [number, CLVMType]; 191 | value_stack.push(r as SExp); 192 | return additional_cost; 193 | } 194 | 195 | const op_stack = [eval_op]; 196 | const value_stack = [program.cons(args)]; 197 | let cost = 0; 198 | 199 | while(op_stack.length){ 200 | const f = op_stack.pop() as (typeof eval_op); 201 | cost += f(op_stack, value_stack); 202 | if(max_cost && cost > max_cost){ 203 | throw new EvalError("cost exceeded", SExp.to(max_cost)); 204 | } 205 | } 206 | return t(cost, value_stack[value_stack.length-1]); 207 | } 208 | -------------------------------------------------------------------------------- /src/serialize.ts: -------------------------------------------------------------------------------- 1 | /* 2 | decoding: 3 | read a byte 4 | if it's 0x80, it's nil (which might be same as 0) 5 | if it's 0xff, it's a cons box. Read two items, build cons 6 | otherwise, number of leading set bits is length in bytes to read size 7 | For example, if the bit fields of the first byte read are: 8 | 10xx xxxx -> 1 byte is allocated for size_byte, and the value of the size is 00xx xxxx 9 | 110x xxxx -> 2 bytes are allocated for size_byte, and the value of the size 000x xxxx xxxx xxxx 10 | 1110 xxxx -> 3 bytes allocated. The size is 0000 xxxx xxxx xxxx xxxx xxxx 11 | 1111 0xxx -> 4 bytes allocated. 12 | 1111 10xx -> 5 bytes allocated. 13 | If the first byte read is one of the following: 14 | 1000 0000 -> 0 bytes : nil 15 | 0000 0000 -> 1 byte : zero (b'\x00') 16 | */ 17 | import {SExp} from "./SExp"; 18 | import {Bytes, Stream, t} from "./__type_compatibility__"; 19 | import {None} from "./__python_types__"; 20 | import {TToSexpF, TValStack} from "./as_javascript"; 21 | import {int_from_bytes} from "./casts"; 22 | import {CLVMObject, CLVMType} from "./CLVMObject"; 23 | 24 | const MAX_SINGLE_BYTE = 0x7F; 25 | const CONS_BOX_MARKER = 0xFF; 26 | 27 | type TOpStack = Array<(op_stack: TOpStack, val_stack: TValStack, f: Stream, to_sexp_f: TToSexpF) => unknown>; 28 | 29 | export function* sexp_to_byte_iterator(sexp: SExp){ 30 | const todo_stack = [sexp]; 31 | while(todo_stack.length){ 32 | sexp = todo_stack.pop() as SExp; 33 | const pair = sexp.pair; 34 | if(pair){ 35 | // yield Bytes.from([CONS_BOX_MARKER]); 36 | yield new Bytes(new Uint8Array([CONS_BOX_MARKER])); 37 | todo_stack.push(pair[1]); 38 | todo_stack.push(pair[0]); 39 | } 40 | else{ 41 | yield* atom_to_byte_iterator(sexp.atom); 42 | } 43 | } 44 | } 45 | 46 | export function* atom_to_byte_iterator(atom: Bytes|None){ 47 | const size = atom ? atom.length : 0; 48 | if(size === 0 || !atom){ 49 | // yield Bytes.from("0x80", "hex"); 50 | yield new Bytes(new Uint8Array([0x80])); 51 | return; 52 | } 53 | else if(size === 1){ 54 | if(atom.at(0) <= MAX_SINGLE_BYTE){ 55 | yield atom; 56 | return; 57 | } 58 | } 59 | 60 | let uint8array; 61 | if(size < 0x40){ 62 | uint8array = Uint8Array.from([0x80 | size]); 63 | } 64 | else if(size < 0x2000){ 65 | uint8array = Uint8Array.from([ 66 | 0xC0 | (size >> 8), 67 | (size >> 0) & 0xFF, 68 | ]); 69 | } 70 | else if(size < 0x100000){ 71 | uint8array = Uint8Array.from([ 72 | 0xE0 | (size >> 16), 73 | (size >> 8) & 0xFF, 74 | (size >> 0) & 0xFF, 75 | ]); 76 | } 77 | else if(size < 0x8000000){ 78 | uint8array = Uint8Array.from([ 79 | 0xF0 | (size >> 24), 80 | (size >> 16) & 0xFF, 81 | (size >> 8) & 0xFF, 82 | (size >> 0) & 0xFF, 83 | ]); 84 | } 85 | else if(size < 0x400000000){ 86 | uint8array = Uint8Array.from([ 87 | 0xF8 | ((size / 2**32) | 0),// (size >> 32), 88 | ((size / 2**24) | 0) & 0xFF, 89 | ((size / 2**16) | 0) & 0xFF, 90 | ((size / 2**8) | 0) & 0xFF, 91 | ((size / 2**0) | 0) & 0xFF, 92 | ]); 93 | } 94 | else{ 95 | throw new Error(`sexp too long ${atom}`); 96 | } 97 | const size_blob = new Bytes(uint8array); 98 | 99 | yield size_blob; 100 | yield atom; 101 | return; 102 | } 103 | 104 | export function sexp_to_stream(sexp: SExp, f: Stream){ 105 | for(const b of sexp_to_byte_iterator(sexp)){ 106 | f.write(b); 107 | } 108 | } 109 | 110 | function _op_read_sexp(op_stack: TOpStack, val_stack: TValStack, f: Stream, to_sexp_f: TToSexpF){ 111 | const blob = f.read(1); 112 | if(blob.length === 0){ 113 | throw new Error("bad encoding"); 114 | } 115 | const b = blob.at(0); 116 | if(b === CONS_BOX_MARKER){ 117 | op_stack.push(_op_cons); 118 | op_stack.push(_op_read_sexp); 119 | op_stack.push(_op_read_sexp); 120 | return; 121 | } 122 | val_stack.push(_atom_from_stream(f, b, to_sexp_f)); 123 | } 124 | 125 | function _op_cons(op_stack: TOpStack, val_stack: TValStack, f: Stream, to_sexp_f: TToSexpF){ 126 | const right = val_stack.pop() as SExp; 127 | const left = val_stack.pop() as SExp; 128 | val_stack.push(to_sexp_f(t(left, right))); 129 | } 130 | 131 | export function sexp_from_stream(f: Stream, to_sexp_f: TToSexpF){ 132 | const op_stack: TOpStack = [_op_read_sexp]; 133 | const val_stack: TValStack = []; 134 | 135 | while(op_stack.length){ 136 | const func = op_stack.pop(); 137 | if(func){ 138 | func(op_stack, val_stack, f, ((v: any) => new CLVMObject(v) as CLVMType) as TToSexpF); 139 | } 140 | } 141 | 142 | return to_sexp_f(val_stack.pop() as any); 143 | } 144 | 145 | function _op_consume_sexp(f: Stream){ 146 | const blob = f.read(1); 147 | if(blob.length === 0){ 148 | throw new Error("bad encoding"); 149 | } 150 | const b = blob.at(0); 151 | if(b === CONS_BOX_MARKER){ 152 | return t(blob, 2); 153 | } 154 | return t(_consume_atom(f, b), 0); 155 | } 156 | 157 | function _consume_atom(f: Stream, b: number){ 158 | if(b === 0x80){ 159 | return Bytes.from([b]); 160 | } 161 | else if(b <= MAX_SINGLE_BYTE){ 162 | return Bytes.from([b]); 163 | } 164 | 165 | let bit_count = 0; 166 | let bit_mask = 0x80; 167 | let ll = b; 168 | 169 | while(ll & bit_mask){ 170 | bit_count += 1; 171 | ll &= 0xFF ^ bit_mask; 172 | bit_mask >>= 1; 173 | } 174 | 175 | let size_blob = Bytes.from([ll]); 176 | if(bit_count > 1){ 177 | const ll2 = f.read(bit_count-1); 178 | if(ll2.length !== bit_count-1){ 179 | throw new Error("bad encoding"); 180 | } 181 | size_blob = size_blob.concat(ll2); 182 | } 183 | 184 | const size = int_from_bytes(size_blob); 185 | if(size >= 0x400000000){ 186 | throw new Error("blob too large"); 187 | } 188 | const blob = f.read(size); 189 | if(blob.length !== size){ 190 | throw new Error("bad encoding"); 191 | } 192 | return Bytes.from([b]).concat(size_blob.subarray(1)).concat(blob); 193 | } 194 | 195 | /* 196 | instead of parsing the input stream, this function pulls out all the bytes 197 | that represent on S-expression tree, and returns them. This is more efficient 198 | than parsing and returning a python S-expression tree. 199 | */ 200 | export function sexp_buffer_from_stream(f: Stream): Bytes { 201 | const buffer = new Stream(); 202 | let depth = 1; 203 | while(depth > 0){ 204 | depth -= 1; 205 | const [buf, d] = _op_consume_sexp(f) as [Bytes, number]; 206 | depth += d; 207 | buffer.write(buf); 208 | } 209 | return buffer.getValue(); 210 | } 211 | 212 | function _atom_from_stream(f: Stream, b: number, to_sexp_f: TToSexpF): SExp { 213 | if(b === 0x80){ 214 | return to_sexp_f(Bytes.NULL); 215 | } 216 | else if(b <= MAX_SINGLE_BYTE){ 217 | return to_sexp_f(Bytes.from([b])); 218 | } 219 | let bit_count = 0; 220 | let bit_mask = 0x80; 221 | while(b & bit_mask){ 222 | bit_count += 1; 223 | b &= 0xFF ^ bit_mask; 224 | bit_mask >>= 1; 225 | } 226 | let size_blob = Bytes.from([b]); 227 | if(bit_count > 1){ 228 | const bin = f.read(bit_count - 1); 229 | if(bin.length !== bit_count - 1){ 230 | throw new Error("bad encoding"); 231 | } 232 | size_blob = size_blob.concat(bin); 233 | } 234 | const size = int_from_bytes(size_blob); 235 | if(size >= 0x400000000){ 236 | throw new Error("blob too large"); 237 | } 238 | const blob = f.read(size); 239 | if(blob.length !== size){ 240 | throw new Error("bad encoding"); 241 | } 242 | return to_sexp_f(blob); 243 | } -------------------------------------------------------------------------------- /tests/_benchmark.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | if(!fs.existsSync(path.resolve(__dirname, "..", ".dist", "npm"))){ 5 | console.error("You need to build first"); 6 | process.exit(0); 7 | return; 8 | } 9 | 10 | const {now} = require("perf_hooks").performance; 11 | 12 | /* 13 | # Equivalent Python code 14 | import time 15 | bi = 0x1b199f15c1a55ba3e1b53361c79f19dd33778f7f81a7d5bd0de76f21491be96fd385057191c9551551f93deb51bdffeb0d498b2df5dbfdd9d92b8129ad9dbfd963a349235f8b19f329db63bd75157b5bf3774d09b7f3cd75073d63e90d77ed977bc1d761698115fbc57bb9830b292551095517e95b9d351b4b4fa1713745b3ed6f6571d3c745596f4727f3659f6f07a3ff7bd3f18fe7f57da54f412b4505c9172fc71dfb2fd5add50d43d7c5e5edf7c325e50545b50703c7e5478d7579a905a571256b0be76bbdb77d977dd711273781af57373ba3571fab2fef4f31930d674dcdad8da5b577956b1753dd2df9c5f9f9bf6d7b7f616389478b2515a5bd4d3d39cdf1e7e383fdc12327e71f63d971d9d13d514f9f91631bbba91f59a9736f5547975f5d71214fa347ebdf791dd7d33f69f55f6ba72fc7897da19519193f893d1b0f179799b94d379d6b87617b3331d78359f7f9276fdf9d632fb91d31a9b19155d37de3219f11cbe7a1098d8f298f43056f4923b199dd4dc5e1ddbdad9725c3bbaf2f7bd757ad23111bbded59e54f6bad858f0bf9bf8f197b6fe517373563f1bbb375a10307a935730d25b52947ad9b3969416f4be9a7656dfd7bab3f1fcb158b03d58f43cf832dfbb9fdef87a9f951133faf039f1b55e949fdcd41e72d97c3ad79a5bf37bbd17bd3d90565f1e51f8fe30f496943abd795610b3b39f5710709d9af3da53bf1d74951477923dffd21b32f11f3b9fd2b716b93d97d957bfda5fd41377db7c1d523b1391779ed4933d93b7d9513bbcb8daf45f3377dd9fd31138da149db1f81095b6135eba7d99ff5c509df9db9df6b1557e3cdf9037f058bdbc9117d3f55413751eb852d796d711513dd8d6b6f794fc39335c15f2bafed23992b711fd561f79f635dafed3799350d5387b94315918b45cbbf2f578dad93ddb7637749eda5197345536f319593958b45f78d8d8d71b30d2fc76b256919d5879d9da349b3d92f0fe1373ba531738b83056565554331c713e98b691f132399279137375149476ba947a3735931d76d679b4f77691dd35393a161dd3ba3937dc5d1790f69893593ff43fdbf91777faddbd18bf1b967e56de1c5410d418ddbf313558fd56b076f69095fcf4dcd8bf7354b39212f1b712781ddaf934777ad6faf5f87ed95d585e7675b59d3bbcbf7656565ed4b718def0767991f6d9385533f15b5fbfde7fb5f41490f897d4bb543136b110f879f819fd179df49173de19d71658385253b4967416375a7ed159f47330d7305a77bb9ddb36fc76b0b4f17f567b72b4551a70111e70b2f097bcded3da1fb656179d96fbba949bd231f5d6db961e199b77bef571fe7b79bc339b12db7c9cda9b70d3d9dd7514ba7f9f71993d9dfe1237fcf4549e75d6bf9a5af13379b99318f076da9c1954dbd1df377cd394f25cb4f5d6195f92b77bbcd6959a3cf67412583850b59919d49a37de5c3b7610301cf0b3be71545b78fa31b73a9d9db939d0915ff3b4fd95fd32f0fb3a9af0df3b73f6f994beff97b170d19f1f5fb6b57c353852be3adfbc1711907e73743673bc527c3dfab47b77bfb0d7d65c94da56147cfcd753dd3e309df7b5f7f518d31717dcd4f913521570183d751736531fb5f41d3719505bb9b85db09bdbfc779f17987cd7765b1cdc1df2fa5790947bb61811fa10b21df6bd33549ad43df93af5365d95f8bdfbfd103e907559fb393a5e3897df1797f315f330561d391836f3123eb494fcf191d61ad6931cb4d2113517375c5b31583dbe195c125e9e1fbcf89fd494145c1794f4319a5857baf7d19dfc3016dab3d85a187f7373fbb7b45875ba3e33d61e9a5e95915a181299b0d0f1f818de11f6b0f1fb317c9fd1bdd4dd7b30517333b15eda983e3ab271381c35dab4521b97fdb9bd7b383efadeb0139ef377ba5432597edeff127279d9d8b7bfb259bbfb9177381d907eb07593f2da921b5 16 | st = time.perf_counter() 17 | for i in range(5): 18 | bi *= bi 19 | print('time: %.10f ms' % ((time.perf_counter() - st)*1000.0)) 20 | */ 21 | function profile_bigint(){ 22 | let bi = BigInt("0x1b199f15c1a55ba3e1b53361c79f19dd33778f7f81a7d5bd0de76f21491be96fd385057191c9551551f93deb51bdffeb0d498b2df5dbfdd9d92b8129ad9dbfd963a349235f8b19f329db63bd75157b5bf3774d09b7f3cd75073d63e90d77ed977bc1d761698115fbc57bb9830b292551095517e95b9d351b4b4fa1713745b3ed6f6571d3c745596f4727f3659f6f07a3ff7bd3f18fe7f57da54f412b4505c9172fc71dfb2fd5add50d43d7c5e5edf7c325e50545b50703c7e5478d7579a905a571256b0be76bbdb77d977dd711273781af57373ba3571fab2fef4f31930d674dcdad8da5b577956b1753dd2df9c5f9f9bf6d7b7f616389478b2515a5bd4d3d39cdf1e7e383fdc12327e71f63d971d9d13d514f9f91631bbba91f59a9736f5547975f5d71214fa347ebdf791dd7d33f69f55f6ba72fc7897da19519193f893d1b0f179799b94d379d6b87617b3331d78359f7f9276fdf9d632fb91d31a9b19155d37de3219f11cbe7a1098d8f298f43056f4923b199dd4dc5e1ddbdad9725c3bbaf2f7bd757ad23111bbded59e54f6bad858f0bf9bf8f197b6fe517373563f1bbb375a10307a935730d25b52947ad9b3969416f4be9a7656dfd7bab3f1fcb158b03d58f43cf832dfbb9fdef87a9f951133faf039f1b55e949fdcd41e72d97c3ad79a5bf37bbd17bd3d90565f1e51f8fe30f496943abd795610b3b39f5710709d9af3da53bf1d74951477923dffd21b32f11f3b9fd2b716b93d97d957bfda5fd41377db7c1d523b1391779ed4933d93b7d9513bbcb8daf45f3377dd9fd31138da149db1f81095b6135eba7d99ff5c509df9db9df6b1557e3cdf9037f058bdbc9117d3f55413751eb852d796d711513dd8d6b6f794fc39335c15f2bafed23992b711fd561f79f635dafed3799350d5387b94315918b45cbbf2f578dad93ddb7637749eda5197345536f319593958b45f78d8d8d71b30d2fc76b256919d5879d9da349b3d92f0fe1373ba531738b83056565554331c713e98b691f132399279137375149476ba947a3735931d76d679b4f77691dd35393a161dd3ba3937dc5d1790f69893593ff43fdbf91777faddbd18bf1b967e56de1c5410d418ddbf313558fd56b076f69095fcf4dcd8bf7354b39212f1b712781ddaf934777ad6faf5f87ed95d585e7675b59d3bbcbf7656565ed4b718def0767991f6d9385533f15b5fbfde7fb5f41490f897d4bb543136b110f879f819fd179df49173de19d71658385253b4967416375a7ed159f47330d7305a77bb9ddb36fc76b0b4f17f567b72b4551a70111e70b2f097bcded3da1fb656179d96fbba949bd231f5d6db961e199b77bef571fe7b79bc339b12db7c9cda9b70d3d9dd7514ba7f9f71993d9dfe1237fcf4549e75d6bf9a5af13379b99318f076da9c1954dbd1df377cd394f25cb4f5d6195f92b77bbcd6959a3cf67412583850b59919d49a37de5c3b7610301cf0b3be71545b78fa31b73a9d9db939d0915ff3b4fd95fd32f0fb3a9af0df3b73f6f994beff97b170d19f1f5fb6b57c353852be3adfbc1711907e73743673bc527c3dfab47b77bfb0d7d65c94da56147cfcd753dd3e309df7b5f7f518d31717dcd4f913521570183d751736531fb5f41d3719505bb9b85db09bdbfc779f17987cd7765b1cdc1df2fa5790947bb61811fa10b21df6bd33549ad43df93af5365d95f8bdfbfd103e907559fb393a5e3897df1797f315f330561d391836f3123eb494fcf191d61ad6931cb4d2113517375c5b31583dbe195c125e9e1fbcf89fd494145c1794f4319a5857baf7d19dfc3016dab3d85a187f7373fbb7b45875ba3e33d61e9a5e95915a181299b0d0f1f818de11f6b0f1fb317c9fd1bdd4dd7b30517333b15eda983e3ab271381c35dab4521b97fdb9bd7b383efadeb0139ef377ba5432597edeff127279d9d8b7bfb259bbfb9177381d907eb07593f2da921b5"); 23 | let st = now(); 24 | // [multiplication and memory allocation] 25 | for(let i=0;i<5;i++){ 26 | bi *= bi; 27 | } 28 | // [multiplication only] 29 | // const bi2 = bi * bi; 30 | return [now() - st]; 31 | } 32 | 33 | function profile_bigint_to_byte(){ 34 | const {bigint_to_bytes, bigint_from_bytes} = require("../.dist/npm"); 35 | let bi = BigInt("0x1b199f15c1a55ba3e1b53361c79f19dd33778f7f81a7d5bd0de76f21491be96fd385057191c9551551f93deb51bdffeb0d498b2df5dbfdd9d92b8129ad9dbfd963a349235f8b19f329db63bd75157b5bf3774d09b7f3cd75073d63e90d77ed977bc1d761698115fbc57bb9830b292551095517e95b9d351b4b4fa1713745b3ed6f6571d3c745596f4727f3659f6f07a3ff7bd3f18fe7f57da54f412b4505c9172fc71dfb2fd5add50d43d7c5e5edf7c325e50545b50703c7e5478d7579a905a571256b0be76bbdb77d977dd711273781af57373ba3571fab2fef4f31930d674dcdad8da5b577956b1753dd2df9c5f9f9bf6d7b7f616389478b2515a5bd4d3d39cdf1e7e383fdc12327e71f63d971d9d13d514f9f91631bbba91f59a9736f5547975f5d71214fa347ebdf791dd7d33f69f55f6ba72fc7897da19519193f893d1b0f179799b94d379d6b87617b3331d78359f7f9276fdf9d632fb91d31a9b19155d37de3219f11cbe7a1098d8f298f43056f4923b199dd4dc5e1ddbdad9725c3bbaf2f7bd757ad23111bbded59e54f6bad858f0bf9bf8f197b6fe517373563f1bbb375a10307a935730d25b52947ad9b3969416f4be9a7656dfd7bab3f1fcb158b03d58f43cf832dfbb9fdef87a9f951133faf039f1b55e949fdcd41e72d97c3ad79a5bf37bbd17bd3d90565f1e51f8fe30f496943abd795610b3b39f5710709d9af3da53bf1d74951477923dffd21b32f11f3b9fd2b716b93d97d957bfda5fd41377db7c1d523b1391779ed4933d93b7d9513bbcb8daf45f3377dd9fd31138da149db1f81095b6135eba7d99ff5c509df9db9df6b1557e3cdf9037f058bdbc9117d3f55413751eb852d796d711513dd8d6b6f794fc39335c15f2bafed23992b711fd561f79f635dafed3799350d5387b94315918b45cbbf2f578dad93ddb7637749eda5197345536f319593958b45f78d8d8d71b30d2fc76b256919d5879d9da349b3d92f0fe1373ba531738b83056565554331c713e98b691f132399279137375149476ba947a3735931d76d679b4f77691dd35393a161dd3ba3937dc5d1790f69893593ff43fdbf91777faddbd18bf1b967e56de1c5410d418ddbf313558fd56b076f69095fcf4dcd8bf7354b39212f1b712781ddaf934777ad6faf5f87ed95d585e7675b59d3bbcbf7656565ed4b718def0767991f6d9385533f15b5fbfde7fb5f41490f897d4bb543136b110f879f819fd179df49173de19d71658385253b4967416375a7ed159f47330d7305a77bb9ddb36fc76b0b4f17f567b72b4551a70111e70b2f097bcded3da1fb656179d96fbba949bd231f5d6db961e199b77bef571fe7b79bc339b12db7c9cda9b70d3d9dd7514ba7f9f71993d9dfe1237fcf4549e75d6bf9a5af13379b99318f076da9c1954dbd1df377cd394f25cb4f5d6195f92b77bbcd6959a3cf67412583850b59919d49a37de5c3b7610301cf0b3be71545b78fa31b73a9d9db939d0915ff3b4fd95fd32f0fb3a9af0df3b73f6f994beff97b170d19f1f5fb6b57c353852be3adfbc1711907e73743673bc527c3dfab47b77bfb0d7d65c94da56147cfcd753dd3e309df7b5f7f518d31717dcd4f913521570183d751736531fb5f41d3719505bb9b85db09bdbfc779f17987cd7765b1cdc1df2fa5790947bb61811fa10b21df6bd33549ad43df93af5365d95f8bdfbfd103e907559fb393a5e3897df1797f315f330561d391836f3123eb494fcf191d61ad6931cb4d2113517375c5b31583dbe195c125e9e1fbcf89fd494145c1794f4319a5857baf7d19dfc3016dab3d85a187f7373fbb7b45875ba3e33d61e9a5e95915a181299b0d0f1f818de11f6b0f1fb317c9fd1bdd4dd7b30517333b15eda983e3ab271381c35dab4521b97fdb9bd7b383efadeb0139ef377ba5432597edeff127279d9d8b7bfb259bbfb9177381d907eb07593f2da921b5"); 36 | const st1 = now(); 37 | let by = bigint_to_bytes(bi); 38 | const eta1 = now() - st1; 39 | const st2 = now(); 40 | let bi2 = bigint_from_bytes(by); 41 | const eta2 = now() - st2; 42 | const st3 = now(); 43 | const eq = bi === bi2; 44 | const eta3 = now() - st3; 45 | return [eta1, eta2, eta3]; 46 | } 47 | 48 | const profiles = { 49 | profile_bigint, 50 | profile_bigint_to_byte, 51 | }; 52 | 53 | /** 54 | * @param {string} profile_name 55 | * @param {number} n 56 | */ 57 | function run_profile(profile_name, n){ 58 | const results = []; 59 | for(let i=0;i { 5 | test("signed: true", () => { 6 | expect(int_from_bytes(None, {signed: true})).toBe(0); 7 | expect(int_from_bytes(h("01"), {signed: true})).toBe(1); 8 | expect(int_from_bytes(h("7f"), {signed: true})).toBe(127); 9 | expect(int_from_bytes(h("80"), {signed: true})).toBe(-128); 10 | expect(int_from_bytes(h("ff"), {signed: true})).toBe(-1); 11 | expect(int_from_bytes(h("00ffff00"), {signed: true})).toBe(16776960); 12 | expect(int_from_bytes(h("ffffffff"), {signed: true})).toBe(-1); 13 | expect(() => int_from_bytes(h("ffffffffffffffff"), {signed: true})).toThrow(); 14 | }); 15 | test("signed: false", () => { 16 | expect(int_from_bytes(None, {signed: false})).toBe(0); 17 | expect(int_from_bytes(h("01"), {signed: false})).toBe(1); 18 | expect(int_from_bytes(h("7f"), {signed: false})).toBe(127); 19 | expect(int_from_bytes(h("80"), {signed: false})).toBe(128); 20 | expect(int_from_bytes(h("ff"), {signed: false})).toBe(255); 21 | expect(int_from_bytes(h("ffffffff"), {signed: false})).toBe(4294967295); 22 | expect(() => int_from_bytes(h("ffffffffffffffff"), {signed: false})).toThrow(); 23 | }); 24 | test("default option", () => { 25 | expect(int_from_bytes(None)).toBe(int_from_bytes(None, {signed: false})); 26 | expect(int_from_bytes(h("01"))).toBe(int_from_bytes(h("01"), {signed: false})); 27 | expect(int_from_bytes(h("7f"))).toBe(int_from_bytes(h("7f"), {signed: false})); 28 | expect(int_from_bytes(h("80"))).toBe(int_from_bytes(h("80"), {signed: false})); 29 | expect(int_from_bytes(h("ff"))).toBe(int_from_bytes(h("ff"), {signed: false})); 30 | expect(int_from_bytes(h("ffffffff"))).toBe(int_from_bytes(h("ffffffff"), {signed: false})); 31 | expect(() => int_from_bytes(h("ffffffffffffffff"))).toThrow(); 32 | }); 33 | }) 34 | 35 | describe("bigint_from_bytes", () => { 36 | test("signed: true", () => { 37 | expect(bigint_from_bytes(None, {signed: true}) === BigInt(0)).toBeTruthy(); 38 | expect(bigint_from_bytes(h("01"), {signed: true}) === BigInt(1)).toBeTruthy(); 39 | expect(bigint_from_bytes(h("7f"), {signed: true}) === BigInt(127)).toBeTruthy(); 40 | expect(bigint_from_bytes(h("80"), {signed: true}) === BigInt(-128)).toBeTruthy(); 41 | expect(bigint_from_bytes(h("ff"), {signed: true}) === BigInt(-1)).toBeTruthy(); 42 | expect(bigint_from_bytes(h("ffffffff"), {signed: true}) === BigInt(-1)).toBeTruthy(); 43 | expect(bigint_from_bytes(h("fedcba987654"), {signed: true}) === BigInt("-1250999896492")).toBeTruthy(); 44 | expect(bigint_from_bytes(h("ffffffffffffffff"), {signed: true}) === BigInt(-1)).toBeTruthy(); 45 | expect(bigint_from_bytes(h("7fffffffffffffff"), {signed: true}) === BigInt(2)**BigInt(63) - BigInt(1)).toBeTruthy(); 46 | }); 47 | test("signed: false", () => { 48 | expect(bigint_from_bytes(None, {signed: false}) === BigInt(0)).toBeTruthy(); 49 | expect(bigint_from_bytes(h("01"), {signed: false}) === BigInt(1)).toBeTruthy(); 50 | expect(bigint_from_bytes(h("7f"), {signed: false}) === BigInt(127)).toBeTruthy(); 51 | expect(bigint_from_bytes(h("80"), {signed: false}) === BigInt(128)).toBeTruthy(); 52 | expect(bigint_from_bytes(h("ff"), {signed: false}) === BigInt(255)).toBeTruthy(); 53 | expect(bigint_from_bytes(h("ffffffff"), {signed: false}) === BigInt(4294967295)).toBeTruthy(); 54 | expect(bigint_from_bytes(h("fedcba987654"), {signed: false}) === BigInt("0xfedcba987654")).toBeTruthy(); 55 | expect(bigint_from_bytes(h("ffffffffffffffff"), {signed: false}) === BigInt("0xffffffffffffffff")).toBeTruthy(); 56 | expect(bigint_from_bytes(h("7fffffffffffffff"), {signed: false}) === BigInt(2)**BigInt(63) - BigInt(1)).toBeTruthy(); 57 | }); 58 | test("default option", () => { 59 | expect(bigint_from_bytes(None) === bigint_from_bytes(None, {signed: false})).toBeTruthy(); 60 | expect(bigint_from_bytes(h("01")) === bigint_from_bytes(h("01"), {signed: false})).toBeTruthy(); 61 | expect(bigint_from_bytes(h("7f")) === bigint_from_bytes(h("7f"), {signed: false})).toBeTruthy(); 62 | expect(bigint_from_bytes(h("80")) === bigint_from_bytes(h("80"), {signed: false})).toBeTruthy(); 63 | expect(bigint_from_bytes(h("ff")) === bigint_from_bytes(h("ff"), {signed: false})).toBeTruthy(); 64 | expect(bigint_from_bytes(h("ffffffff")) === bigint_from_bytes(h("ffffffff"), {signed: false})).toBeTruthy(); 65 | expect(bigint_from_bytes(h("fedcba987654")) === bigint_from_bytes(h("fedcba987654"), {signed: false})).toBeTruthy(); 66 | expect(bigint_from_bytes(h("ffffffffffffffff"), {signed: false}) === BigInt("18446744073709551615")).toBeTruthy(); 67 | expect(bigint_from_bytes(h("7fffffffffffffff")) === bigint_from_bytes(h("7fffffffffffffff"), {signed: false})).toBeTruthy(); 68 | }); 69 | }); 70 | 71 | describe("int_to_bytes", () => { 72 | test("signed: true", () => { 73 | expect(int_to_bytes(0, {signed: true}).equal_to(h(""))).toBeTruthy(); 74 | expect(int_to_bytes(1, {signed: true}).equal_to(h("01"))).toBeTruthy(); 75 | expect(int_to_bytes(-1, {signed: true}).equal_to(h("ff"))).toBeTruthy(); 76 | expect(int_to_bytes(127, {signed: true}).equal_to(h("7f"))).toBeTruthy(); 77 | expect(int_to_bytes(-128, {signed: true}).equal_to(h("80"))).toBeTruthy(); 78 | expect(int_to_bytes(255, {signed: true}).equal_to(h("00ff"))).toBeTruthy(); 79 | expect(int_to_bytes(-256, {signed: true}).equal_to(h("ff00"))).toBeTruthy(); 80 | expect(int_to_bytes(65535, {signed: true}).equal_to(h("00ffff"))).toBeTruthy(); 81 | expect(int_to_bytes(65536, {signed: true}).equal_to(h("010000"))).toBeTruthy(); 82 | expect(int_to_bytes(-65535, {signed: true}).equal_to(h("ff0001"))).toBeTruthy(); 83 | expect(int_to_bytes(-65534, {signed: true}).equal_to(h("ff0002"))).toBeTruthy(); 84 | expect(int_to_bytes(-65536, {signed: true}).equal_to(h("ff0000"))).toBeTruthy(); 85 | expect(int_to_bytes(-559038737, {signed: true}).equal_to(h("deadbeef"))).toBeTruthy(); 86 | }); 87 | test("signed: false", () => { 88 | expect(int_to_bytes(0, {signed: false}).equal_to(h(""))).toBeTruthy(); 89 | expect(int_to_bytes(1, {signed: false}).equal_to(h("01"))).toBeTruthy(); 90 | expect(() => int_to_bytes(-1, {signed: false})).toThrow(); 91 | expect(int_to_bytes(127, {signed: false}).equal_to(h("7f"))).toBeTruthy(); 92 | expect(() => int_to_bytes(-128, {signed: false})).toThrow(); 93 | expect(int_to_bytes(255, {signed: false}).equal_to(h("ff"))).toBeTruthy(); 94 | expect(() => int_to_bytes(-256, {signed: false})).toThrow(); 95 | expect(int_to_bytes(65535, {signed: false}).equal_to(h("ffff"))).toBeTruthy(); 96 | expect(int_to_bytes(65536, {signed: false}).equal_to(h("010000"))).toBeTruthy(); 97 | expect(() => int_to_bytes(-65535, {signed: false})).toThrow(); 98 | expect(int_to_bytes(3735928559, {signed: false}).equal_to(h("deadbeef"))).toBeTruthy(); 99 | }); 100 | test("default option", () => { 101 | expect(int_to_bytes(0).equal_to(int_to_bytes(0, {signed: false}))).toBeTruthy(); 102 | expect(int_to_bytes(1).equal_to(int_to_bytes(1, {signed: false}))).toBeTruthy(); 103 | expect(() => int_to_bytes(-1)).toThrow(); 104 | expect(int_to_bytes(127).equal_to(int_to_bytes(127, {signed: false}))).toBeTruthy(); 105 | expect(() => int_to_bytes(-128)).toThrow(); 106 | expect(int_to_bytes(255).equal_to(int_to_bytes(255, {signed: false}))).toBeTruthy(); 107 | expect(() => int_to_bytes(-256)).toThrow(); 108 | expect(int_to_bytes(65535).equal_to(int_to_bytes(65535, {signed: false}))).toBeTruthy(); 109 | expect(int_to_bytes(65536).equal_to(int_to_bytes(65536, {signed: false}))).toBeTruthy(); 110 | expect(() => int_to_bytes(-65535)).toThrow(); 111 | expect(int_to_bytes(3735928559).equal_to(int_to_bytes(3735928559, {signed: false}))).toBeTruthy(); 112 | }); 113 | }); 114 | 115 | test("pow", () => { 116 | expect(pow(BigInt(2), BigInt(10))).toBe(BigInt(1024)); 117 | expect(pow(BigInt(1024), BigInt(1))).toBe(BigInt(1024)); 118 | expect(pow(BigInt(256), BigInt(0))).toBe(BigInt(1)); 119 | expect(pow(BigInt(0), BigInt(0))).toBe(BigInt(1)); 120 | expect(pow(BigInt(0), BigInt(1))).toBe(BigInt(0)); 121 | expect(pow(BigInt(-2), BigInt(3))).toBe(BigInt(-8)); 122 | expect(pow(BigInt(-2), BigInt(2))).toBe(BigInt(4)); 123 | }); 124 | 125 | describe("bigint_to_bytes", () => { 126 | test("signed: true", () => { 127 | expect(bigint_to_bytes(BigInt(0), {signed: true}).equal_to(h(""))).toBeTruthy(); 128 | expect(bigint_to_bytes(BigInt(1), {signed: true}).equal_to(h("01"))).toBeTruthy(); 129 | expect(bigint_to_bytes(BigInt(-1), {signed: true}).equal_to(h("ff"))).toBeTruthy(); 130 | expect(bigint_to_bytes(BigInt(127), {signed: true}).equal_to(h("7f"))).toBeTruthy(); 131 | expect(bigint_to_bytes(BigInt(-128), {signed: true}).equal_to(h("80"))).toBeTruthy(); 132 | expect(bigint_to_bytes(BigInt(255), {signed: true}).equal_to(h("00ff"))).toBeTruthy(); 133 | expect(bigint_to_bytes(BigInt(-256), {signed: true}).equal_to(h("ff00"))).toBeTruthy(); 134 | expect(bigint_to_bytes(BigInt(65535), {signed: true}).equal_to(h("00ffff"))).toBeTruthy(); 135 | expect(bigint_to_bytes(BigInt(65536), {signed: true}).equal_to(h("010000"))).toBeTruthy(); 136 | expect(bigint_to_bytes(BigInt(-65535), {signed: true}).equal_to(h("ff0001"))).toBeTruthy(); 137 | expect(bigint_to_bytes(BigInt(-65534), {signed: true}).equal_to(h("ff0002"))).toBeTruthy(); 138 | expect(bigint_to_bytes(BigInt(-65536), {signed: true}).equal_to(h("ff0000"))).toBeTruthy(); 139 | expect(bigint_to_bytes(BigInt(-559038737), {signed: true}).equal_to(h("deadbeef"))).toBeTruthy(); 140 | expect(bigint_to_bytes(BigInt("-2401053088876216593"), {signed: true}).equal_to(h("deadbeefdeadbeef"))).toBeTruthy(); 141 | expect(bigint_to_bytes( 142 | BigInt("0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001"), {signed: true}).equal_to( 143 | h("0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001") 144 | ) 145 | ).toBeTruthy(); 146 | }); 147 | test("signed: false", () => { 148 | expect(bigint_to_bytes(BigInt(0), {signed: false}).equal_to(h(""))).toBeTruthy(); 149 | expect(bigint_to_bytes(BigInt(1), {signed: false}).equal_to(h("01"))).toBeTruthy(); 150 | expect(() => bigint_to_bytes(BigInt(-1), {signed: false})).toThrow(); 151 | expect(bigint_to_bytes(BigInt(127), {signed: false}).equal_to(h("7f"))).toBeTruthy(); 152 | expect(() => bigint_to_bytes(BigInt(-128), {signed: false})).toThrow(); 153 | expect(bigint_to_bytes(BigInt(255), {signed: false}).equal_to(h("ff"))).toBeTruthy(); 154 | expect(() => bigint_to_bytes(BigInt(-256), {signed: false})).toThrow(); 155 | expect(bigint_to_bytes(BigInt(65535), {signed: false}).equal_to(h("ffff"))).toBeTruthy(); 156 | expect(bigint_to_bytes(BigInt(65536), {signed: false}).equal_to(h("010000"))).toBeTruthy(); 157 | expect(() => bigint_to_bytes(BigInt(-65535), {signed: false})).toThrow(); 158 | expect(bigint_to_bytes(BigInt(3735928559), {signed: false}).equal_to(h("deadbeef"))).toBeTruthy(); 159 | expect(bigint_to_bytes(BigInt("16045690984833335023"), {signed: false}).equal_to(h("deadbeefdeadbeef"))).toBeTruthy(); 160 | expect(bigint_to_bytes( 161 | BigInt("0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001"), {signed: false}).equal_to( 162 | h("0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001") 163 | ) 164 | ).toBeTruthy(); 165 | }); 166 | test("default option", () => { 167 | expect(bigint_to_bytes(BigInt(0)).equal_to(bigint_to_bytes(BigInt(0), {signed: false}))).toBeTruthy(); 168 | expect(bigint_to_bytes(BigInt(1)).equal_to(bigint_to_bytes(BigInt(1), {signed: false}))).toBeTruthy(); 169 | expect(() => bigint_to_bytes(BigInt(-1))).toThrow(); 170 | expect(bigint_to_bytes(BigInt(127)).equal_to(bigint_to_bytes(BigInt(127), {signed: false}))).toBeTruthy(); 171 | expect(() => bigint_to_bytes(BigInt(-128))).toThrow(); 172 | expect(bigint_to_bytes(BigInt(255)).equal_to(bigint_to_bytes(BigInt(255), {signed: false}))).toBeTruthy(); 173 | expect(() => bigint_to_bytes(BigInt(-256))).toThrow(); 174 | expect(bigint_to_bytes(BigInt(65535)).equal_to(bigint_to_bytes(BigInt(65535), {signed: false}))).toBeTruthy(); 175 | expect(bigint_to_bytes(BigInt(65536)).equal_to(bigint_to_bytes(BigInt(65536), {signed: false}))).toBeTruthy(); 176 | expect(() => bigint_to_bytes(BigInt(-65535))).toThrow(); 177 | expect(bigint_to_bytes(BigInt(3735928559)).equal_to(bigint_to_bytes(BigInt(3735928559), {signed: false}))).toBeTruthy(); 178 | expect( 179 | bigint_to_bytes(BigInt("0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001")).equal_to( 180 | bigint_to_bytes(BigInt("0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001"), {signed: false}) 181 | ) 182 | ).toBeTruthy(); 183 | }); 184 | }); 185 | 186 | test("limbs_for_int", () => { 187 | expect(limbs_for_int(0)).toBe(0); 188 | expect(limbs_for_int(1)).toBe(1); 189 | expect(limbs_for_int(-255)).toBe(1); 190 | expect(limbs_for_int(255)).toBe(1); 191 | expect(limbs_for_int(256)).toBe(2); 192 | expect(limbs_for_int(256)).toBe(2); 193 | expect(limbs_for_int(-65535)).toBe(2); 194 | expect(limbs_for_int(65535)).toBe(2); 195 | expect(limbs_for_int(65536)).toBe(3); 196 | expect(limbs_for_int(-65536)).toBe(3); 197 | }); 198 | -------------------------------------------------------------------------------- /tests/_type_compatibility_test.ts: -------------------------------------------------------------------------------- 1 | import {Bytes, b, h} from "../src/__type_compatibility__"; 2 | 3 | describe("Bytes", () => { 4 | test("0x00 > b('')", () => { 5 | expect(h("0x00").compare(b(""))).toBe(1); 6 | }); 7 | test("b('') < 0x00", () => { 8 | expect(b("").compare(h("0x00"))).toBe(-1); 9 | }); 10 | test("0x00 < 0x0000", () => { 11 | expect(h("0x00").compare(h("0x0000"))).toBe(-1); 12 | }); 13 | test("0x1000 > 0x10", () => { 14 | expect(h("0x1000").compare(h("0x10"))).toBe(1); 15 | }); 16 | test("0x0010 < 0x10", () => { 17 | expect(h("0x0010").compare(h("0x10"))).toBe(-1); 18 | }); 19 | test("0x1000 < 0x20", () => { 20 | expect(h("0x1000").compare(h("0x20"))).toBe(-1); 21 | }); 22 | test("0x2000 > 0x20", () => { 23 | expect(h("0x2000").compare(h("0x20"))).toBe(1); 24 | }); 25 | test("0x2000 < 0x21", () => { 26 | expect(h("0x2000").compare(h("0x21"))).toBe(-1); 27 | }); 28 | test("0x0011 > 0x0010", () => { 29 | expect(h("0x0011").compare(h("0x0010"))).toBe(1); 30 | }); 31 | test("0x4433221144332211 == 0x4433221144332211", () => { 32 | expect(h("0x4433221144332211").compare(h("0x4433221144332211"))).toBe(0); 33 | }); 34 | test("0x4433221144332211 > 0x4433221144002211", () => { 35 | expect(h("0x4433221144332211").compare(h("0x4433221144002211"))).toBe(1); 36 | }); 37 | test("0x4433221144332211 < 0x4433221144332212", () => { 38 | expect(h("0x4433221144332211").compare(h("0x4433221144332212"))).toBe(-1); 39 | }); 40 | test("0x4433221144332212 > 0x4433221144332211", () => { 41 | expect(h("0x4433221144332212").compare(h("0x4433221144002211"))).toBe(1); 42 | }); 43 | test("0xfedcba9876543210fedcba9876543210fedcba9876543210 == 0xfedcba9876543210fedcba9876543210fedcba9876543210", () => { 44 | expect(h("0xfedcba9876543210fedcba9876543210fedcba9876543210").compare(h("0xfedcba9876543210fedcba9876543210fedcba9876543210"))).toBe(0); 45 | }); 46 | test("0xfedcba9876543210fedcbaAA76543210fedcba9876543210 > 0xfedcba9876543210fedcba9876543210fedcba9876543210", () => { 47 | expect(h("0xfedcba9876543210fedcbaAA76543210fedcba9876543210").compare(h("0xfedcba9876543210fedcba9876543210fedcba9876543210"))).toBe(1); 48 | }); 49 | test("0xfedcba9876543210fedcba9876543210fedcba9876543210 < 0xffdcba987654", () => { 50 | expect(h("0xfedcba9876543210fedcba9876543210fedcba9876543210").compare(h("0xffdcba987654"))).toBe(-1); 51 | }); 52 | test("b('') == b('')", () => { 53 | expect(b("").compare(b(""))).toBe(0); 54 | }); 55 | test("0x414243 == b('ABC')", () => { 56 | expect(h("0x414243").compare(b("ABC"))).toBe(0); 57 | }); 58 | }) 59 | -------------------------------------------------------------------------------- /tests/as_javascript_test.ts: -------------------------------------------------------------------------------- 1 | import {SExp, t, h, b, Bytes, getBLSModule, initialize, None, Tuple, list, str, repr} from "../src"; 2 | import {CLVMObject} from "../src/CLVMObject"; 3 | import {EvalError} from "../src/EvalError"; 4 | import type {ModuleInstance} from "bls-signatures"; 5 | 6 | let BLS: ModuleInstance; 7 | 8 | class dummy_class { 9 | i: number; 10 | 11 | constructor() { 12 | this.i = 0; 13 | } 14 | } 15 | 16 | function gen_tree(depth: number): SExp { 17 | if(depth === 0){ 18 | return SExp.to(1337); 19 | } 20 | const subtree = gen_tree(depth - 1); 21 | return SExp.to(t(subtree, subtree)); 22 | } 23 | 24 | const fh = h; 25 | const H01 = fh("01"); 26 | const H02 = fh("02"); 27 | 28 | 29 | 30 | function check_as_javascript(p: any){ 31 | const v = SExp.to(p); 32 | const p1 = v.as_javascript(); 33 | expect(p).toEqual(p1); 34 | } 35 | 36 | beforeAll(async () => { 37 | await initialize(); 38 | BLS = getBLSModule(); 39 | }); 40 | 41 | test("test_null", () => { 42 | check_as_javascript(b("")); 43 | }); 44 | 45 | test("test_embedded_tuples", () => { 46 | check_as_javascript(t(b("10"), t(t(b("200"), b("300")), b("400")))); 47 | }); 48 | 49 | test("test_single_bytes", () => { 50 | for(let _=0;_<256;_++){ 51 | check_as_javascript(Bytes.from([_])); 52 | } 53 | }); 54 | 55 | test("test_short_lists", () => { 56 | check_as_javascript(b("")); 57 | for(let _=0;_<256;_++){ 58 | check_as_javascript(Bytes.from([_])); 59 | } 60 | }); 61 | 62 | test("test_int", () => { 63 | const v = SExp.to(42); 64 | expect(v.atom?.equal_to(Bytes.from([42]))).toBeTruthy(); 65 | }); 66 | 67 | test("test_none", () => { 68 | const v = SExp.to(None); 69 | expect(v.atom?.equal_to(b(""))).toBeTruthy(); 70 | }); 71 | 72 | test("test_empty_list", () => { 73 | const v = SExp.to([]); 74 | expect(v.atom?.equal_to(b(""))).toBeTruthy(); 75 | }); 76 | 77 | test("test_list_of_one", () => { 78 | const v = SExp.to([1]); 79 | expect((v.pair as any)[0] instanceof CLVMObject).toBeTruthy(); 80 | expect((v.pair as any)[1] instanceof CLVMObject).toBeTruthy(); 81 | expect((v.as_pair() as any)[0] instanceof SExp).toBeTruthy(); 82 | expect((v.as_pair() as any)[1] instanceof SExp).toBeTruthy(); 83 | expect((v.pair as any)[0].atom.equal_to(h("01"))).toBeTruthy(); 84 | expect((v.pair as any)[1].atom.equal_to(b(""))).toBeTruthy(); 85 | }); 86 | 87 | test("test_g1element", () => { 88 | const b = fh( 89 | "b3b8ac537f4fd6bde9b26221d49b54b17a506be147347dae5" 90 | + "d081c0a6572b611d8484e338f3432971a9823976c6a232b" 91 | ); 92 | const v = SExp.to(BLS.G1Element.from_bytes(b.raw())); 93 | expect(v.atom?.equal_to(b)).toBeTruthy(); 94 | }); 95 | 96 | test("test_complex", () => { 97 | check_as_javascript(t(b(""), b("foo"))); 98 | check_as_javascript(t(b(""), b("1"))); 99 | check_as_javascript([b("2"), t(b(""), b("1"))]); 100 | check_as_javascript([b(""), b("2"), t(b(""), b("1"))]); 101 | check_as_javascript( 102 | [b(""), b("1"), b("2"), [b("30"), b("40"), b("90")], b("600"), t(b(""), b("18"))] 103 | ); 104 | }); 105 | 106 | test("test_listp", () => { 107 | expect(SExp.to(42).listp()).toBeFalsy(); 108 | expect(SExp.to(b("")).listp()).toBeFalsy(); 109 | expect(SExp.to(b("1337")).listp()).toBeFalsy(); 110 | 111 | expect(SExp.to(t(1337, 42)).listp()).toBeTruthy(); 112 | expect(SExp.to([1337, 42]).listp()).toBeTruthy(); 113 | }); 114 | 115 | test("test_nullp", () => { 116 | expect(SExp.to(b("")).nullp()).toBeTruthy(); 117 | expect(SExp.to(b("1337")).nullp()).toBeFalsy(); 118 | expect(SExp.to(t(b(""), b(""))).nullp()).toBeFalsy(); 119 | }); 120 | 121 | test("test_constants", () => { 122 | expect(SExp.__NULL__.nullp()).toBeTruthy(); 123 | expect(SExp.null().nullp()).toBeTruthy(); 124 | expect(SExp.TRUE.equal_to(true as any)).toBeTruthy(); 125 | expect(SExp.FALSE.equal_to(false as any)).toBeTruthy(); 126 | }); 127 | 128 | test("test_list_len", () => { 129 | let v = SExp.to(42); 130 | for(let i=0;i<100;i++){ 131 | expect(v.list_len()).toBe(i); 132 | v = SExp.to(t(42, v)); 133 | } 134 | expect(v.list_len()).toBe(100); 135 | }); 136 | 137 | test("test_list_len_atom", () => { 138 | const v = SExp.to(42); 139 | expect(v.list_len()).toBe(0); 140 | }); 141 | 142 | test("test_as_int", () => { 143 | expect(SExp.to(fh("80")).as_int()).toBe(-128); 144 | expect(SExp.to(fh("ff")).as_int()).toBe(-1); 145 | expect(SExp.to(fh("0080")).as_int()).toBe(128); 146 | expect(SExp.to(fh("00ff")).as_int()).toBe(255); 147 | }); 148 | 149 | test("test_as_bigint", () => { 150 | expect(SExp.to(fh("80")).as_bigint()).toBe(BigInt(-128)); 151 | expect(SExp.to(fh("ff")).as_bigint()).toBe(BigInt(-1)); 152 | expect(SExp.to(fh("0080")).as_bigint()).toBe(BigInt(128)); 153 | expect(SExp.to(fh("00ff")).as_bigint()).toBe(BigInt(255)); 154 | }); 155 | 156 | test("test_cons", () => { 157 | // list 158 | expect( 159 | SExp.to(H01).cons(SExp.to(H02).cons(SExp.null())).as_javascript(), 160 | ).toEqual([H01, H02]); 161 | // cons-box of two values 162 | expect(SExp.to(H01).cons(SExp.to(H02).as_javascript()).equal_to(t(H01, H02))).toBeTruthy(); 163 | }); 164 | 165 | test("test_string", () => { 166 | expect(SExp.to("foobar").atom?.equal_to(b("foobar"))).toBeTruthy(); 167 | }); 168 | 169 | test("test_deep_recursion", () => { 170 | let d = b("2") as any; 171 | for(let i=0;i<1000;i++){ 172 | d = [d]; 173 | } 174 | let v = SExp.to(d); 175 | for(let i=0;i<1000;i++){ 176 | expect((((v.as_pair() as any)[1]).atom as SExp).equal_to(SExp.null())).toBeTruthy(); 177 | v = (v.as_pair() as any)[0]; 178 | d = d[0]; 179 | } 180 | 181 | expect(v.atom?.equal_to(b("2"))); 182 | expect(d.equal_to(b("2"))); 183 | }); 184 | 185 | test("test_long_linked_list", () => { 186 | let d = b("") as any; 187 | for(let i=0;i<1000;i++){ 188 | d = t(b("2"), d); 189 | } 190 | let v = SExp.to(d); 191 | for(let i=0;i<1000;i++){ 192 | expect((v.as_pair() as any)[0].atom.equal_to(d[0])).toBeTruthy(); 193 | v = (v.as_pair() as any)[1]; 194 | d = d[1]; 195 | } 196 | 197 | expect(v.atom?.equal_to(SExp.null())).toBeTruthy(); 198 | expect(d.equal_to(b(""))).toBeTruthy(); 199 | }); 200 | 201 | test("test_long_list", () => { 202 | const d = [...new Array(1000)].map(() => 1337); 203 | let v = SExp.to(d); 204 | for(let i=0;i<(1000-1);i++){ 205 | expect((v.as_pair() as Tuple)[0].as_int()).toBe(d[i]); 206 | v = (v.as_pair() as Tuple)[1]; 207 | } 208 | 209 | // expect(v.atom?.equal_to(SExp.null())).toBeTruthy(); // v.atom is `None`. In javascript, None is just a null and not implement `equal_to` function 210 | expect(SExp.null().equal_to(v.atom)).toBeTruthy(); 211 | }); 212 | 213 | test("test_invalid_type", () => { 214 | expect(() => { 215 | SExp.to(dummy_class as any); 216 | }).toThrowError(Error); 217 | }); 218 | 219 | test("test_invalid_tuple", () => { 220 | expect(() => { 221 | SExp.to(t(dummy_class, dummy_class)); 222 | }).toThrowError(Error); 223 | 224 | expect(() => { 225 | SExp.to(t(dummy_class, dummy_class)); 226 | }).toThrowError(Error); 227 | }); 228 | 229 | test("test_clvm_object_tuple", () => { 230 | const o1 = new CLVMObject(b("foo")); 231 | const o2 = new CLVMObject(b("bar")); 232 | expect(SExp.to(t(o1, o2)).equal_to(t(o1, o2))).toBeTruthy(); 233 | }); 234 | 235 | test("test_first", () => { 236 | let val = SExp.to(1); 237 | expect(() => val.first()).toThrowError(EvalError); 238 | val = SExp.to(t(42, val)); 239 | expect(val.first().equal_to(SExp.to(42))); 240 | }); 241 | 242 | test("test_rest", () => { 243 | let val = SExp.to(1); 244 | expect(() => val.rest()).toThrowError(EvalError); 245 | val = SExp.to(t(42, val)); 246 | expect(val.rest().equal_to(SExp.to(1))); 247 | }); 248 | 249 | test("test_as_iter", () => { 250 | function assertArrayEqual(subject: any[], expected: any[]){ 251 | expect(subject.length).toBe(expected.length); 252 | expect(subject.every((v, i) => v.equal_to(expected[i]))).toBeTruthy(); 253 | } 254 | 255 | let val = list(SExp.to(t(1, t(2, t(3, t(4, b("")))))).as_iter()); 256 | assertArrayEqual(val, [1,2,3,4]); 257 | 258 | val = list(SExp.to(b("")).as_iter()); 259 | assertArrayEqual(val, []); 260 | 261 | val = list(SExp.to(t(1, b(""))).as_iter()); 262 | assertArrayEqual(val, [1]); 263 | 264 | // these fail because the lists are not null-terminated 265 | expect(() => list(SExp.to(1).as_iter())).toThrowError(EvalError); 266 | expect(() => { 267 | list(SExp.to(t(1, t(2, t(3, t(4, 5))))).as_iter()); 268 | }).toThrowError(EvalError); 269 | }); 270 | 271 | test("test_eq", () => { 272 | const val = SExp.to(1); 273 | 274 | expect(val.equal_to(1)).toBeTruthy(); 275 | expect(val.equal_to(2)).toBeFalsy(); 276 | 277 | // mismatching types 278 | expect(val.equal_to([1])).toBeFalsy(); 279 | expect(val.equal_to([1, 2])).toBeFalsy(); 280 | expect(val.equal_to(t(1, 2))).toBeFalsy(); 281 | expect(val.equal_to(t(dummy_class, dummy_class))).toBeFalsy(); 282 | }); 283 | 284 | test("test_eq_tree", () => { 285 | const val1 = gen_tree(2); 286 | const val2 = gen_tree(2); 287 | const val3 = gen_tree(3); 288 | 289 | expect(val1.equal_to(val2)).toBeTruthy(); 290 | expect(val2.equal_to(val1)).toBeTruthy(); 291 | expect(val1.equal_to(val3)).toBeFalsy(); 292 | expect(val3.equal_to(val1)).toBeFalsy(); 293 | }); 294 | 295 | test("test_str", () => { 296 | expect(str(SExp.to(1))).toBe("01"); 297 | expect(str(SExp.to(1337))).toBe("820539"); 298 | expect(str(SExp.to(-1))).toBe("81ff"); 299 | expect(str(SExp.to(gen_tree(1)))).toBe("ff820539820539"); 300 | expect(str(SExp.to(gen_tree(2)))).toBe("ffff820539820539ff820539820539"); 301 | }); 302 | 303 | test("test_repr", () => { 304 | expect(repr(SExp.to(1))).toBe("SExp(01)"); 305 | expect(repr(SExp.to(1337))).toBe("SExp(820539)"); 306 | expect(repr(SExp.to(-1))).toBe("SExp(81ff)"); 307 | expect(repr(SExp.to(gen_tree(1)))).toBe("SExp(ff820539820539)"); 308 | expect(repr(SExp.to(gen_tree(2)))).toBe("SExp(ffff820539820539ff820539820539)"); 309 | }); 310 | -------------------------------------------------------------------------------- /tests/bls12_381_test.ts: -------------------------------------------------------------------------------- 1 | import {initializeBLS} from "../src/__bls_signatures__"; 2 | import {Bytes} from "../src/__type_compatibility__"; 3 | 4 | let BLS: ReturnType extends Promise ? T : never; 5 | beforeAll(async () => { 6 | BLS = await initializeBLS(); 7 | }); 8 | 9 | test("test_stream", () => { 10 | const {G1Element, PrivateKey} = BLS; 11 | 12 | for(let _=1;_<64;_++){ 13 | const _ToBytes = Uint8Array.from([...new Array(32)].map((v,k) => k < 31 ? 0 : _)); 14 | const p = PrivateKey.from_bytes(_ToBytes, false).get_g1(); 15 | const blob = Bytes.from(p, "G1Element"); 16 | const p1 = G1Element.from_bytes(blob.raw()); 17 | expect(blob.length).toBe(48); 18 | expect(p.equal_to(p1)); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /tests/operatordict_test.ts: -------------------------------------------------------------------------------- 1 | import {OperatorDict} from "../src/operators"; 2 | 3 | test("test_operatordict_constructor", () => { 4 | /* 5 | Constructing should fail if quote or apply are not specified, 6 | either by object property or by keyword argument. 7 | Note that they cannot be specified in the operator dictionary itself. 8 | */ 9 | const d = {1: "hello", 2: "goodbye"}; 10 | expect(() => OperatorDict(d as any)).toThrow(); 11 | expect(() => OperatorDict(d as any, {apply_atom: 1 as any})).toThrow(); 12 | expect(() => OperatorDict(d as any, {quote_atom: 1 as any})).toThrow(); 13 | const o = OperatorDict(d as any, {apply_atom: 1 as any, quote_atom: 2 as any}); 14 | console.log(o); 15 | 16 | // Why does the constructed Operator dict contain entries for "apply":1 and "quote":2 ? 17 | // assert d == o 18 | expect(o.apply_atom).toBe(1); 19 | expect(o.quote_atom).toBe(2); 20 | 21 | // Test construction from an already existing OperatorDict 22 | const o2 = OperatorDict(o as any); 23 | expect(o2.apply_atom).toBe(1); 24 | expect(o2.quote_atom).toBe(2); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/operators_test.ts: -------------------------------------------------------------------------------- 1 | import {OPERATOR_LOOKUP, KEYWORD_TO_ATOM, default_unknown_op, OperatorDict} from "../src/operators"; 2 | import {EvalError} from "../src/EvalError"; 3 | import {Bytes, SExp, t, b, h} from "../src"; 4 | import {CONCAT_BASE_COST} from "../src/costs"; 5 | 6 | let handler_called = false; 7 | 8 | function unknown_handler(name: Bytes, args: SExp){ 9 | handler_called = true; 10 | expect(name.equal_to(h("0xffff").concat(b("1337")))).toBeTruthy(); 11 | expect(args.equal_to(SExp.to(1337))).toBeTruthy(); 12 | return t(42, SExp.to(b("foobar"))); 13 | } 14 | 15 | test("test_unknown_op", () => { 16 | expect(() => { 17 | OPERATOR_LOOKUP(h("0xffff").concat(b("1337")), SExp.to(1337)) 18 | }).toThrowError(EvalError); 19 | const od = OperatorDict(OPERATOR_LOOKUP, {unknown_op_handler: (name, args) => unknown_handler(name, args)}); 20 | const [cost, ret] = od(h("0xffff").concat(b("1337")), SExp.to(1337)); 21 | expect(handler_called).toBeTruthy(); 22 | expect(cost).toBe(42); 23 | expect(ret.equal_to(SExp.to(b("foobar")))); 24 | }); 25 | 26 | test("test_plus", () => { 27 | console.log(OPERATOR_LOOKUP); 28 | expect(OPERATOR_LOOKUP(KEYWORD_TO_ATOM["+"], SExp.to([3,4,5]))[1].equal_to(SExp.to(12))).toBeTruthy(); 29 | }); 30 | 31 | test("test_unknown_op_reserved", () => { 32 | // any op that starts with ffff is reserved, and results in a hard 33 | // failure 34 | expect(() => { 35 | default_unknown_op(h("0xffff"), SExp.null()); 36 | }).toThrowError(EvalError); 37 | 38 | for(const suffix of [h("ff"), b("0"), h("00"), h("ccccfeedface")]){ 39 | expect(() => { 40 | default_unknown_op(h("ffff").concat(suffix), SExp.null()); 41 | }).toThrowError(EvalError); 42 | } 43 | 44 | expect(() => { 45 | // an empty atom is not a valid opcode 46 | default_unknown_op(b(""), SExp.null()); 47 | }).toThrowError(EvalError); 48 | 49 | // a single ff is not sufficient to be treated as a reserved opcode 50 | expect(default_unknown_op(h("ff"), SExp.null())).toEqual(t(CONCAT_BASE_COST, SExp.null())); 51 | 52 | // leading zeroes count, and this does not count as a ffff-prefix 53 | // the cost is 0xffff00 = 16776960 54 | expect(default_unknown_op(h("00ffff0000"), SExp.null())).toEqual(t(16776961, SExp.null())); 55 | }); 56 | 57 | test("test_unknown_ops_last_bits", () => { 58 | // The last byte is ignored for no-op unknown ops 59 | for(const suffix of [h("3f"), h("0f"), h("00"), h("2c")]){ 60 | // the cost is unchanged by the last byte 61 | expect(default_unknown_op(h("3c").concat(suffix), SExp.null())).toEqual(t(61, SExp.null())); 62 | } 63 | }); 64 | -------------------------------------------------------------------------------- /tests/original/clvm_wasm_test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | initializeClvmWasm, 3 | h, 4 | SExp, 5 | run_chia_program, 6 | Flag, 7 | LazyNode, 8 | CLVMType, 9 | Bytes, 10 | Tuple, 11 | } from "../../src/index"; 12 | 13 | beforeAll(() => { 14 | return initializeClvmWasm(); 15 | }); 16 | 17 | test("run transactions generator", async () => { 18 | /* 19 | This comes from a block of height 600043 in the testnet11. 20 | You can get the block data by: 21 | chia rpc full_node get_blocks '{"start": 600043, "end": 600044}' 22 | on testnet11. 23 | The transactions_generator in the block was serialized with backref enabled. 24 | */ 25 | const transactions_generator = h( 26 | "0xff01ffffffa09dc16b766b557d0d8d94fe1ee636245b4417a46cd53bd4e70c26a62dc698d406ffff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04fffe84016b6b7fff80808080fffe820db78080fe81ffffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101fe6f80ff0180fe3e80ffff04ffff01b0a282b4b0117a8d04906835fa4fa0e13d3fbd1dd61899ebdf5157977611d1bae52f2ea97cbc3916466b1c9176d30a9030fe3f80ff85727e956731ffff80ffff01ffff33ffa05597ef68eaf171a6303995ecbb14fdbf2c24300b625bfbc886ea68270424661dff5880ffff33ffa07a7a9cb053b9e7086ddbb789a4a1abc646a06627d372eca59368bf90c15028bfff85727b9a765980ffff34ff8402faf08080ffff3cffa0d71f4c45af09583209498dbb9974bbda21b859fac0bf3348337ed33a2ba5c3838080ff8080808080" 27 | ).raw(); 28 | 29 | // Deserializing WITHOUT allowing backref throws an Error 30 | expect(() => LazyNode.from_bytes(transactions_generator)).toThrow(); 31 | 32 | // Deserializing WITH allowing backref doesn't throw an Error 33 | const node = LazyNode.from_bytes_with_backref(transactions_generator); 34 | // `new SExp(...)` accepts any objects which has `atom` and `pair` attributes. 35 | const sexp = new SExp(node as CLVMType); 36 | const sexp_as_js = sexp.as_javascript(); 37 | expect(sexp_as_js).toBeInstanceOf(Array); 38 | // `transactions_generator` should be in the form (q . (generators...)) 39 | expect(((sexp_as_js as Tuple)[0]).equal_to(h("0x01"))); // q: 0x01 40 | 41 | const program = transactions_generator; 42 | const env = SExp.to([]).as_bin().raw(); // 0x80 43 | const max_cost = BigInt(20); 44 | 45 | // If you don't set a flag to allow backref, it throws an Error. 46 | expect(() => run_chia_program(program, env, max_cost, 0)).toThrow(); 47 | 48 | const flag = Flag.allow_backrefs(); 49 | // If you set a flag to allow backref, it doesn't throw an Error. 50 | const cost_and_result = run_chia_program(program, env, max_cost, flag); 51 | 52 | expect(cost_and_result).toBeInstanceOf(Array); 53 | expect(cost_and_result[0]).toBeGreaterThan(BigInt(0)); 54 | expect(cost_and_result[1]).toHaveProperty("atom"); 55 | expect(cost_and_result[1]).toHaveProperty("pair"); 56 | }); 57 | -------------------------------------------------------------------------------- /tests/run_program_test.ts: -------------------------------------------------------------------------------- 1 | import {msb_mask} from "../src/run_program"; 2 | 3 | test("test_msb_mask", () => { 4 | expect(msb_mask(0x0)).toBe(0x0); 5 | expect(msb_mask(0x01)).toBe(0x01); 6 | expect(msb_mask(0x02)).toBe(0x02); 7 | expect(msb_mask(0x04)).toBe(0x04); 8 | expect(msb_mask(0x08)).toBe(0x08); 9 | expect(msb_mask(0x10)).toBe(0x10); 10 | expect(msb_mask(0x20)).toBe(0x20); 11 | expect(msb_mask(0x40)).toBe(0x40); 12 | expect(msb_mask(0x80)).toBe(0x80); 13 | 14 | expect(msb_mask(0x44)).toBe(0x40); 15 | expect(msb_mask(0x2a)).toBe(0x20); 16 | expect(msb_mask(0xff)).toBe(0x80); 17 | expect(msb_mask(0x0f)).toBe(0x08); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/serialize_test.ts: -------------------------------------------------------------------------------- 1 | import {to_sexp_f} from "../src/index"; 2 | import { 3 | sexp_from_stream, 4 | sexp_buffer_from_stream, 5 | atom_to_byte_iterator, 6 | } from "../src/serialize"; 7 | import {Bytes, h, b, t, Stream} from "../src/__type_compatibility__"; 8 | 9 | const TEXT = b("the quick brown fox jumps over the lazy dogs"); 10 | 11 | class InfiniteStream extends Stream { 12 | private _buf: Bytes; 13 | public constructor(b: Bytes) { 14 | super(b); 15 | this._buf = b; 16 | } 17 | 18 | public read(n: number){ 19 | let ret = b(""); 20 | while(n > 0 && this._buf.length > 0){ 21 | ret = ret.concat(this._buf.subarray(0,1)); 22 | this._buf = this._buf.subarray(1); 23 | n -= 1; 24 | } 25 | ret = ret.concat(b(" ").repeat(n)); 26 | return ret; 27 | } 28 | } 29 | 30 | class LargeAtom extends Bytes { 31 | public get length(): number { 32 | return 0x400000001; 33 | } 34 | } 35 | 36 | function check_serde(s: any){ 37 | const v = to_sexp_f(s); 38 | let b = v.as_bin(); 39 | let v1 = sexp_from_stream(new Stream(b), to_sexp_f); 40 | if(!v.equal_to(v1)){ 41 | console.log(`${v}: ${b.length} ${b} ${v1}`); 42 | debugger; 43 | b = v.as_bin(); 44 | v1 = sexp_from_stream(new Stream(b), to_sexp_f); 45 | } 46 | expect(v.equal_to(v1)).toBeTruthy(); 47 | // this copies the bytes that represent a single s-expression, just to 48 | // know where the message ends. It doesn't build a javascript representation 49 | // of it 50 | const buf = sexp_buffer_from_stream(new Stream(b)); 51 | expect(buf.equal_to(b)).toBeTruthy(); 52 | } 53 | 54 | test("test_zero", () => { 55 | const v = to_sexp_f(h("0x00")); 56 | expect(v.as_bin().equal_to(h("0x00"))).toBeTruthy(); 57 | }); 58 | 59 | test("test_empty", () => { 60 | const v = to_sexp_f(b("")); 61 | expect(v.as_bin().equal_to(h("0x80"))).toBeTruthy(); 62 | }); 63 | 64 | test("test_empty_string", () => { 65 | check_serde(b("")); 66 | }); 67 | 68 | test("test_single_bytes", () =>{ 69 | for(let _=0;_<256;_++){ 70 | check_serde(Bytes.from([_])); 71 | } 72 | }); 73 | 74 | test("test_short_list", () =>{ 75 | check_serde([]); 76 | for(let _=0;_<2048;_+=8){ 77 | for(let size=1;size<5;size++){ 78 | check_serde([...new Array(size)].map(() => _)); 79 | } 80 | } 81 | }); 82 | 83 | test("test_long_blobs", () =>{ 84 | let text = b(""); 85 | text = text.repeat(300); 86 | 87 | for(let _=0;_{ 94 | expect(() => { 95 | for(const b of atom_to_byte_iterator(new LargeAtom())){ 96 | console.log(`${b}`); 97 | } 98 | }).toThrow(); 99 | }); 100 | 101 | test("test_very_long_blobs", () => { 102 | for(const size of [0x40, 0x2000, 0x100000, 0x8000000]){ 103 | const count = (size / TEXT.length) | 0; 104 | let text = TEXT.repeat(count); 105 | expect(text.length).toBeLessThan(size); 106 | check_serde(text); 107 | text = TEXT.repeat(count+1); 108 | expect(text.length).toBeGreaterThan(size); 109 | check_serde(text); 110 | } 111 | }); 112 | 113 | test("test_very_deep_tree", () => { 114 | const blob = b("a"); 115 | for(const depth of [10, 100, 1000, 10000, 100000]){ 116 | let s = to_sexp_f(blob); 117 | for(let _=0;_ { 125 | const bytes_in = b(""); 126 | expect(() => { 127 | sexp_from_stream(new Stream(bytes_in), to_sexp_f); 128 | }).toThrow(); 129 | 130 | expect(() => { 131 | sexp_buffer_from_stream(new Stream(bytes_in)); 132 | }).toThrow(); 133 | }); 134 | 135 | test("test_deserialize_truncated_size", () => { 136 | // fe means the total number of bytes in the length-prefix is 7 137 | // one for each bit set. 5 bytes is too few 138 | const bytes_in = h("0xfe").concat(b(" ")); 139 | expect(() => { 140 | sexp_from_stream(new Stream(bytes_in), to_sexp_f); 141 | }).toThrow(); 142 | 143 | expect(() => { 144 | sexp_buffer_from_stream(new Stream(bytes_in)); 145 | }).toThrow(); 146 | }); 147 | 148 | test("test_deserialize_truncated_blob", () => { 149 | // this is a complete length prefix. The blob is supposed to be 63 bytes 150 | // the blob itself is truncated though, it's less than 63 bytes 151 | const bytes_in = h("0xbf").concat(b(" ")); 152 | expect(() => { 153 | sexp_from_stream(new Stream(bytes_in), to_sexp_f); 154 | }).toThrow(); 155 | 156 | expect(() => { 157 | sexp_buffer_from_stream(new Stream(bytes_in)); 158 | }).toThrow(); 159 | }); 160 | 161 | test("test_deserialize_large_blob", () => { 162 | // this length prefix is 7 bytes long, the last 6 bytes specifies the 163 | // length of the blob, which is 0xffffffffffff, or (2^48 - 1) 164 | // we don't support blobs this large, and we should fail immediately when 165 | // exceeding the max blob size, rather than trying to read this many 166 | // bytes from the stream 167 | const bytes_in = h("0xfe").concat(h("0xff").repeat(6)); 168 | expect(() => { 169 | sexp_from_stream(new InfiniteStream(bytes_in), to_sexp_f); 170 | }).toThrow(); 171 | 172 | expect(() => { 173 | sexp_buffer_from_stream(new InfiniteStream(bytes_in)); 174 | }).toThrow(); 175 | }); 176 | -------------------------------------------------------------------------------- /tests/to_sexp_test.ts: -------------------------------------------------------------------------------- 1 | import {SExp, looks_like_clvm_object, convert_atom_to_bytes} from "../src/SExp"; 2 | import {CLVMObject, CLVMType} from "../src/CLVMObject"; 3 | import {isBytes, isTuple, Tuple, b, Bytes, t} from "../src/__type_compatibility__"; 4 | import {None} from "../src/__python_types__"; 5 | 6 | function validate_sexp(sexp: SExp){ 7 | const validate_stack = [sexp]; 8 | while(validate_stack.length){ 9 | const v = validate_stack.pop() as SExp; 10 | expect(v instanceof SExp).toBeTruthy(); 11 | if(v.pair){ 12 | expect(isTuple(v.pair)).toBeTruthy(); 13 | const [v1, v2] = v.pair; 14 | expect(looks_like_clvm_object(v1)).toBeTruthy(); 15 | expect(looks_like_clvm_object(v2)).toBeTruthy(); 16 | const [s1, s2] = v.as_pair() as Tuple; 17 | validate_stack.push(s1); 18 | validate_stack.push(s2); 19 | } 20 | else{ 21 | expect(isBytes(v.atom)).toBeTruthy(); 22 | } 23 | } 24 | } 25 | 26 | function print_leaves(tree: SExp): string { 27 | const a = tree.atom; 28 | if(a !== None){ 29 | if(a.length === 0){ 30 | return "() "; 31 | } 32 | return `${a.at(0)} `; 33 | } 34 | 35 | let ret = ""; 36 | for(const i of tree.as_pair() as Tuple){ 37 | ret = ret + print_leaves(i); 38 | } 39 | return ret; 40 | } 41 | 42 | function print_tree(tree: SExp): string { 43 | const a = tree.atom; 44 | if(a !== None){ 45 | if(a.length === 0){ 46 | return "() "; 47 | } 48 | return `${a.at(0)} `; 49 | } 50 | 51 | let ret = "("; 52 | for(const i of tree.as_pair() as Tuple){ 53 | ret = ret + print_tree(i); 54 | } 55 | ret += ")"; 56 | return ret; 57 | } 58 | 59 | test("test_cast_1", () => { 60 | // this was a problem in `clvm_tools` and is included 61 | // to prevent regressions 62 | const sexp = SExp.to(b("foo")); 63 | const t1 = SExp.to([1, sexp]); 64 | validate_sexp(t1); 65 | }); 66 | 67 | test("test_wrap_sexp", () => { 68 | // it's a bit of a layer violation that CLVMObject unwraps SExp, but we 69 | // rely on that in a fair number of places for now. We should probably 70 | // work towards phasing that out 71 | const o = new CLVMObject(SExp.to(1)); 72 | expect(o.atom?.equal_to(Bytes.from([1]))).toBeTruthy(); 73 | }); 74 | 75 | test("test_arbitrary_underlying_tree", () => { 76 | // SExp provides a view on top of a tree of arbitrary types, as long as 77 | // those types implement the CLVMObject protocol. This is an example of 78 | // a tree that's generated 79 | class GeneratedTree implements CLVMType { 80 | depth = 4; 81 | val = 0; 82 | 83 | public constructor(depth: number, val: number) { 84 | expect(depth).toBeGreaterThanOrEqual(0); 85 | this.depth = depth; 86 | this.val = val; 87 | } 88 | 89 | public get atom(){ 90 | if(this.depth > 0){ 91 | return None; 92 | } 93 | return Bytes.from([this.val]); 94 | } 95 | 96 | public get pair(){ 97 | if(this.depth === 0){ 98 | return None; 99 | } 100 | const new_depth = this.depth - 1; 101 | return t(new GeneratedTree(new_depth, this.val), new GeneratedTree(new_depth, this.val + 2**new_depth)); 102 | } 103 | } 104 | 105 | let tree = SExp.to(new GeneratedTree(5, 0)); 106 | expect(print_leaves(tree)).toBe("0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 " + 107 | "16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 "); 108 | 109 | tree = SExp.to(new GeneratedTree(3, 0)); 110 | expect(print_leaves(tree)).toBe("0 1 2 3 4 5 6 7 "); 111 | 112 | tree = SExp.to(new GeneratedTree(3, 10)); 113 | expect(print_leaves(tree)).toBe("10 11 12 13 14 15 16 17 "); 114 | }); 115 | 116 | test("test_looks_like_clvm_object", () => { 117 | // this function can't look at the values, that would cause a cascade of 118 | // eager evaluation/conversion 119 | class dummy {} 120 | 121 | let obj = new dummy() as any; 122 | obj.pair = None; 123 | obj.atom = None; 124 | // console.log(obj); 125 | expect(looks_like_clvm_object(obj)).toBeTruthy(); 126 | 127 | obj = new dummy(); 128 | obj.pair = None; 129 | expect(looks_like_clvm_object(obj)).toBeFalsy(); 130 | 131 | obj = new dummy(); 132 | obj.atom = None; 133 | expect(looks_like_clvm_object(obj)).toBeFalsy(); 134 | }); 135 | 136 | test("test_list_conversions", () => { 137 | const a = SExp.to([1, 2, 3]); 138 | expect(print_tree(a)).toBe("(1 (2 (3 () )))"); 139 | }); 140 | 141 | test("test_string_conversions", () => { 142 | const a = SExp.to("foobar"); 143 | expect(a.atom?.equal_to(b("foobar"))).toBeTruthy(); 144 | }); 145 | 146 | test("test_int_conversions", () => { 147 | const a = SExp.to(1337); 148 | expect(a.atom?.equal_to(Bytes.from([0x5, 0x39]))).toBeTruthy(); 149 | }); 150 | 151 | test("test_none_conversions", () => { 152 | const a = SExp.to(None); 153 | expect(a.atom?.equal_to(b(""))).toBeTruthy(); 154 | }); 155 | 156 | test("test_empty_list_conversions", () => { 157 | const a = SExp.to([]); 158 | expect(a.atom?.equal_to(b(""))).toBeTruthy(); 159 | }); 160 | 161 | test("test_eager_conversion", () => { 162 | expect(() => { 163 | SExp.to(t("foobar", t(1, {}))); 164 | }).toThrow(); 165 | }); 166 | 167 | test("test_convert_atom", () => { 168 | expect(convert_atom_to_bytes(0x133742).equal_to(Bytes.from([0x13, 0x37, 0x42]))).toBeTruthy(); 169 | expect(convert_atom_to_bytes(0x833742).equal_to(Bytes.from([0x00, 0x83, 0x37, 0x42]))).toBeTruthy(); 170 | expect(convert_atom_to_bytes(0).equal_to(b(""))).toBeTruthy(); 171 | 172 | expect(convert_atom_to_bytes("foobar").equal_to(b("foobar"))).toBeTruthy(); 173 | expect(convert_atom_to_bytes("").equal_to(b(""))).toBeTruthy(); 174 | 175 | expect(convert_atom_to_bytes(b("foobar")).equal_to(b("foobar"))).toBeTruthy(); 176 | expect(convert_atom_to_bytes(None).equal_to(b(""))).toBeTruthy(); 177 | expect(convert_atom_to_bytes([]).equal_to(b(""))).toBeTruthy(); 178 | 179 | class DummyByteConvertible { 180 | toBytes(): Bytes { 181 | return b("foobar"); 182 | } 183 | } 184 | 185 | expect(convert_atom_to_bytes(new DummyByteConvertible()).equal_to(b("foobar"))).toBeTruthy(); 186 | 187 | expect(() => { 188 | convert_atom_to_bytes([1, 2, 3]); 189 | }).toThrow(); 190 | 191 | expect(() => { 192 | convert_atom_to_bytes(t(1, 2)); 193 | }).toThrow(); 194 | 195 | expect(() => { 196 | convert_atom_to_bytes({}); 197 | }).toThrow(); 198 | }); 199 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./.dist/npm", 4 | "module": "CommonJS", 5 | "target": "es2016", 6 | "allowJs": true, 7 | "lib": [ 8 | "dom", 9 | "dom.iterable", 10 | "esnext" 11 | ], 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "sourceMap": true, 15 | "skipLibCheck": true, 16 | "noImplicitAny": true, 17 | "declaration": true, 18 | "newLine": "lf" 19 | }, 20 | "include": [ 21 | "src/**/*" 22 | ], 23 | "exclude": ["node_modules"] 24 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | // const {CleanWebpackPlugin} = require("clean-webpack-plugin"); 4 | const TerserPlugin = require("terser-webpack-plugin"); 5 | const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); 6 | 7 | module.exports = { 8 | mode: "production", 9 | context: __dirname, // to automatically find tsconfig.json 10 | plugins: [/*new CleanWebpackPlugin()*/], 11 | devtool: false, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.tsx?$/, 16 | use: [ 17 | {loader: "ts-loader", options: {transpileOnly: true, configFile: "tsconfig.json"}}, 18 | ], 19 | exclude: /node_modules/, 20 | }, 21 | ], 22 | }, 23 | resolve: { 24 | extensions: [".tsx", ".ts", ".js"], 25 | fallback: { 26 | "path": false, 27 | "fs": false, 28 | "crypto": false, 29 | "util": false, 30 | }, 31 | alias: { 32 | "clvm_wasm": false, 33 | }, 34 | }, 35 | target: ["web"], 36 | optimization: { 37 | minimizer: [ 38 | new TerserPlugin({ 39 | terserOptions: { 40 | mangle: { 41 | properties: false, 42 | }, 43 | }, 44 | }), 45 | ] 46 | }, 47 | entry: "./src/index.ts", 48 | output: { 49 | path: path.resolve(__dirname, ".dist", "npm", "browser"), 50 | filename: "index.js", 51 | library: ["clvm"], 52 | libraryTarget: "umd", 53 | globalObject: "this", 54 | }, 55 | }; --------------------------------------------------------------------------------