├── .github └── workflows │ ├── release.beta.yml │ ├── release.branch.yml │ ├── release.pr.merge.yml │ ├── server.yaml │ └── test.yaml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── abi ├── Cargo.toml └── src │ ├── lib.rs │ └── publickey.rs ├── coverage.sh ├── docker └── Dockerfile ├── error ├── Cargo.toml └── src │ ├── kind.rs │ ├── lib.rs │ └── span.rs ├── js ├── .eslintrc ├── .gitignore ├── lerna.json ├── package.json ├── src │ ├── ast.ts │ ├── common.ts │ ├── index.ts │ └── validator │ │ └── index.ts ├── tsconfig.json ├── webpack.config.js └── yarn.lock ├── miden-run ├── Cargo.toml └── src │ └── main.rs ├── parser ├── Cargo.lock ├── Cargo.toml ├── build.rs └── src │ ├── ast.rs │ ├── lexer.rs │ ├── lib.rs │ └── polylang.lalrpop ├── prover ├── Cargo.toml └── src │ └── lib.rs ├── server-routes ├── Cargo.toml └── src │ ├── lib.rs │ └── prove.rs ├── server ├── Cargo.toml └── src │ └── main.rs ├── showcase ├── Cargo.toml ├── LICENSE.md ├── README.md ├── examples │ ├── accounts.rs │ ├── binary_search.rs │ ├── city_country.rs │ ├── fibonacci.rs │ ├── hello_world.rs │ └── reverse_array.rs └── src │ └── lib.rs ├── site ├── .env.local.example ├── .github │ └── screenshot.png ├── .gitignore ├── LICENSE.md ├── README.md ├── components │ ├── Code.tsx │ ├── ColorModeSwitcher.tsx │ ├── ColorModeSync.tsx │ ├── Home.tsx │ ├── Logo.tsx │ ├── Navbar.tsx │ ├── Playground.tsx │ ├── PoweredBy.tsx │ ├── Prover.tsx │ ├── Why.tsx │ ├── example.ts │ ├── pkg │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index_bg.js │ │ ├── index_bg.wasm.d.ts │ │ └── index_bg.wasm.js │ ├── polylang.ts │ └── useAsyncCallback.ts ├── img │ ├── logo.svg │ └── made-by │ │ ├── dark.png │ │ └── light.png ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages │ ├── _app.tsx │ ├── _meta.json │ ├── about.mdx │ ├── docs │ │ ├── _meta.json │ │ ├── getting-started │ │ │ ├── _meta.json │ │ │ ├── building-from-source.mdx │ │ │ ├── polylang-cli.mdx │ │ │ └── polylang-lib.mdx │ │ ├── index.mdx │ │ ├── language-features │ │ │ ├── _meta.json │ │ │ ├── bindings.mdx │ │ │ ├── constructors.mdx │ │ │ ├── context.mdx │ │ │ ├── control-flow.mdx │ │ │ ├── errors.mdx │ │ │ ├── fields.mdx │ │ │ ├── fieldtypes.mdx │ │ │ ├── functions.mdx │ │ │ ├── general-structure.mdx │ │ │ ├── lexical-structure.mdx │ │ │ └── permissions.mdx │ │ └── zk-proofs │ │ │ ├── _meta.json │ │ │ ├── polylang-and-zk.mdx │ │ │ └── what-is-zk.mdx │ ├── index.mdx │ └── playground.mdx ├── public │ ├── img │ │ ├── logo.svg │ │ └── made-by.svg │ └── social.png ├── theme.config.tsx ├── theme.ts ├── tsconfig.json └── yarn.lock ├── src ├── bin │ └── compile │ │ └── main.rs ├── bindings.rs ├── compiler │ ├── array.rs │ ├── boolean.rs │ ├── bytes.rs │ ├── encoder.rs │ ├── float32.rs │ ├── float64.rs │ ├── int32.rs │ ├── int64.rs │ ├── ir.rs │ ├── map.rs │ ├── mod.rs │ ├── nullable.rs │ ├── publickey.rs │ ├── string.rs │ ├── uint32.rs │ └── uint64.rs ├── js.rs ├── lib.rs ├── stableast.rs └── validation.rs ├── test.sh ├── tests ├── Cargo.toml └── src │ ├── col_refs.rs │ ├── lib.rs │ ├── push.rs │ ├── slice.rs │ ├── splice.rs │ ├── string.rs │ └── unshift.rs └── wasm-api ├── Cargo.toml ├── README.md ├── example.js ├── package.json ├── src └── lib.rs ├── webpack.config.js └── yarn.lock /.github/workflows/release.beta.yml: -------------------------------------------------------------------------------- 1 | name: Release (Beta) 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - closed 7 | branches: 8 | - main 9 | 10 | jobs: 11 | release_beta: 12 | runs-on: ubuntu-latest-16-cores 13 | if: github.event.pull_request.merged == true && !startsWith(github.event.pull_request.head.ref, 'release-') 14 | steps: 15 | - uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | 19 | - uses: jetli/wasm-pack-action@v0.4.0 20 | with: 21 | version: "latest" 22 | 23 | - uses: actions/setup-node@v3 24 | with: 25 | node-version: 18.x 26 | cache: 'yarn' 27 | cache-dependency-path: ./js/yarn.lock 28 | 29 | - name: "NPM Identity" 30 | working-directory: ./js 31 | env: 32 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 33 | run: | 34 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc 35 | 36 | - name: Install packages 37 | working-directory: ./js 38 | run: yarn 39 | 40 | - name: Build packages 41 | working-directory: ./js 42 | run: yarn lerna run build 43 | 44 | - name: "Publish" 45 | working-directory: ./js 46 | run: yarn lerna publish --canary --preid beta --dist-tag beta --tag-version-prefix beta --yes 47 | 48 | dispatch: 49 | needs: release_beta 50 | strategy: 51 | matrix: 52 | repo: ['polybase/polybase-ts', 'polybase/polybase-rust', 'polybase/explorer'] 53 | runs-on: ubuntu-latest 54 | steps: 55 | - name: Trigger repo updates 56 | uses: peter-evans/repository-dispatch@v2 57 | with: 58 | token: ${{ secrets.ADMIN_TOKEN }} 59 | repository: ${{ matrix.repo }} 60 | event-type: update-polylang-packages-beta 61 | -------------------------------------------------------------------------------- /.github/workflows/release.branch.yml: -------------------------------------------------------------------------------- 1 | name: Create PR for Release Branch 2 | 3 | on: 4 | create: 5 | branches: 6 | - '**' 7 | 8 | jobs: 9 | create_release_branch: 10 | runs-on: ubuntu-latest 11 | if: startsWith(github.ref, 'refs/heads/release-') 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Set Version as env var 19 | run: | 20 | branch_name=$(echo ${{ github.ref }} | sed 's/refs\/heads\///') 21 | VERSION=${branch_name#release-} 22 | echo "VERSION=$VERSION" >> $GITHUB_ENV 23 | 24 | - uses: actions/setup-node@v3 25 | with: 26 | node-version: 18.x 27 | cache: 'yarn' 28 | cache-dependency-path: ./js/yarn.lock 29 | 30 | - name: Run Lerna version 31 | working-directory: ./js 32 | run: npx lerna version v$VERSION --no-git-tag-version --force-publish --tag-version-prefix='' --yes 33 | 34 | 35 | - name: Commit changes 36 | run: | 37 | git config --local user.email user.email "hello@polybase.xyz" 38 | git config --local user.name "Polybase CI" 39 | git add . 40 | git commit -m "Bump versions" || echo "No changes to commit" 41 | git push 42 | 43 | - name: Create Pull Request 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.ADMIN_TOKEN }} 46 | run: | 47 | branch_name=$(echo ${{ github.ref }} | sed 's/refs\/heads\///') 48 | curl -X POST \ 49 | -H "Authorization: token $GITHUB_TOKEN" \ 50 | -H "Accept: application/vnd.github.v3+json" \ 51 | https://api.github.com/repos/${{ github.repository }}/pulls \ 52 | -d '{ 53 | "title": "Release 'v$VERSION'", 54 | "body": "This is an automated PR for release '$VERSION'", 55 | "head": "'$branch_name'", 56 | "base": "main" 57 | }' 58 | -------------------------------------------------------------------------------- /.github/workflows/release.pr.merge.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - closed 7 | branches: 8 | - main 9 | 10 | jobs: 11 | 12 | release: 13 | runs-on: ubuntu-latest 14 | if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release-') 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | ref: main 20 | token: ${{ secrets.ADMIN_TOKEN }} 21 | 22 | - name: Configure Git 23 | run: | 24 | git config --global user.email "hello@polybase.xyz" 25 | git config --global user.name "Polybase CI" 26 | 27 | - name: Get PR Info 28 | run: | 29 | PR_TITLE="${{ github.event.pull_request.title }}" 30 | PR_DESC="${{ github.event.pull_request.body }}" 31 | PR_BRANCH="${{ github.event.pull_request.head.ref }}" 32 | PR_VERSION="${PR_BRANCH#*release-}" 33 | 34 | echo "PR Title: $PR_TITLE" 35 | echo "PR Description: $PR_DESC" 36 | echo "PR Branch: $PR_BRANCH" 37 | echo "PR Version: $PR_VERSION" 38 | echo "PR_VERSION=$PR_VERSION" >> $GITHUB_ENV 39 | 40 | - uses: jetli/wasm-pack-action@v0.4.0 41 | with: 42 | version: "latest" 43 | 44 | - uses: actions/setup-node@v3 45 | with: 46 | node-version: 18.x 47 | cache: 'yarn' 48 | cache-dependency-path: ./js/yarn.lock 49 | 50 | - name: "NPM Identity" 51 | working-directory: ./js 52 | env: 53 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 54 | run: | 55 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc 56 | 57 | - name: Install packages 58 | working-directory: ./js 59 | run: yarn 60 | 61 | - name: Build packages 62 | working-directory: ./js 63 | run: yarn lerna run build 64 | 65 | - name: "Publish" 66 | working-directory: ./js 67 | run: yarn lerna publish from-package --yes 68 | 69 | - name: "Create Release" 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.ADMIN_TOKEN }} 72 | run: | 73 | curl -X POST \ 74 | --url https://api.github.com/repos/${{ github.repository }}/releases \ 75 | -H "Authorization: Bearer $GITHUB_TOKEN" \ 76 | -H 'Content-Type: application/json' \ 77 | -d "{ 78 | "tag_name": "v'$PR_VERSION'", 79 | "name": "Release v'$PR_VERSION'", 80 | "body": "Release notes for v'$PR_VERSION'" 81 | }" 82 | 83 | dispatch: 84 | needs: release 85 | strategy: 86 | matrix: 87 | repo: ['polybase/polybase-ts', 'polybase/polybase-rust', 'polybase/explorer'] 88 | runs-on: ubuntu-latest 89 | steps: 90 | - name: Trigger repo updates 91 | uses: peter-evans/repository-dispatch@v2 92 | with: 93 | token: ${{ secrets.ADMIN_TOKEN }} 94 | repository: ${{ matrix.repo }} 95 | event-type: update-polylang-packages 96 | -------------------------------------------------------------------------------- /.github/workflows/server.yaml: -------------------------------------------------------------------------------- 1 | name: Server CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Set up Docker Buildx 17 | uses: docker/setup-buildx-action@v1 18 | 19 | - name: Google Cloud Auth 20 | uses: google-github-actions/auth@v1 21 | with: 22 | credentials_json: '${{ secrets.GCP_SA_KEY }}' 23 | 24 | - name: Set up Cloud SDK 25 | uses: google-github-actions/setup-gcloud@v1 26 | 27 | - name: Log in to GCR 28 | run: gcloud auth configure-docker 29 | 30 | - name: Build and push server image 31 | uses: docker/build-push-action@v2 32 | with: 33 | context: . 34 | file: ./docker/Dockerfile 35 | push: true 36 | tags: gcr.io/polybase-internal/polylang-server:latest 37 | 38 | - name: Deploy 39 | run: | 40 | gcloud config set project polybase-internal 41 | gcloud config set run/region us-central1 42 | gcloud run deploy polylang-server --image gcr.io/polybase-internal/polylang-server:latest -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | env: 3 | RUSTFLAGS: "-Dwarnings" 4 | 5 | on: 6 | push: 7 | 8 | jobs: 9 | eslint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 16.x 18 | 19 | - name: Install dependencies 20 | run: yarn install 21 | working-directory: ./site 22 | 23 | - name: Run eslint 24 | run: yarn run lint 25 | working-directory: ./site 26 | 27 | test: 28 | runs-on: ubuntu-latest-16-cores 29 | steps: 30 | - uses: actions/checkout@v3 31 | 32 | - uses: actions-rs/toolchain@v1 33 | with: 34 | toolchain: stable 35 | override: true 36 | 37 | - name: Cargo fmt check 38 | uses: actions-rs/cargo@v1 39 | with: 40 | command: fmt 41 | args: --all --check 42 | 43 | - name: Cargo check 44 | uses: actions-rs/cargo@v1 45 | with: 46 | command: check 47 | args: --workspace --all-targets 48 | 49 | - name: Cargo clippy 50 | uses: actions-rs/cargo@v1 51 | with: 52 | command: clippy 53 | args: --all-targets --all-features 54 | 55 | - name: Run Tests 56 | uses: actions-rs/cargo@v1 57 | timeout-minutes: 10 58 | with: 59 | command: test 60 | args: --workspace --all-features 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .DS_Store 3 | node_modules 4 | showcase/**/*.proof 5 | showcase/**/Cargo.lock 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thank you for taking the time to contribute to `Polylang`! 2 | 3 | The following is a set of guidelines for contributing to `Polylang`. Remember that these are guidelines, not strict rules. So use your judgment, but 4 | adhering to the guidelines as closely as possible will make the contribution process smoother. 5 | 6 | ## What can I contribute? 7 | 8 | We welcome all kinds of contributions - bug reports, feature requests, or enhancement requests. 9 | 10 | ## How to contribute? 11 | 12 | 1. Create a tracking issue in the `Polylang` repo here - https://github.com/polybase/polylang/issues. 13 | 14 | 2. Fork the `Polylang` project, and create a clone from your fork. 15 | 16 | 3. Make the code changes in a branch of your clone. Ensure that you test your changes locally. 17 | 18 | 4. Create a Pull Request (PR) with your code changes linking the issue created in step 1. 19 | 20 | ## Setup 21 | 22 | The `Polylang` repository is a modular `Cargo` [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html) comprising of the following packages with the [package 23 | name](https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field) in parentheses: 24 | 25 | * parser (`polylang_parser`) 26 | * prover (`polylang-prover`) 27 | * abi (`abi`) 28 | * miden-run (`miden-run`) 29 | * error (`error`) 30 | * tests (`tests`) 31 | 32 | in addition to the `Polylang` [root package](https://doc.rust-lang.org/cargo/reference/workspaces.html#root-package). 33 | 34 | The first step is to fork the `Polylang` repository on Github, and clone your fork. Navigate to your clone: 35 | 36 | ```bash 37 | $ cd polylang # this is your clone 38 | ``` 39 | 40 | Now build the project: 41 | 42 | ```bash 43 | $ cargo build 44 | ``` 45 | 46 | Or, for a release build: 47 | 48 | ```bash 49 | $ cargo build --release 50 | ``` 51 | 52 | Run the tests to ensure that everything is working as expected. 53 | 54 | To run all tests: 55 | 56 | ```bash 57 | $ cargo test --workspace --all-targets 58 | ``` 59 | 60 | To run tests for a specific package: 61 | 62 | ```bash 63 | $ cargo test -p 64 | ``` 65 | 66 | Note that the `` must be the value of the `name` field in the package's `Cargo.toml` file, and **not** the name in the workspace's `members` list. 67 | 68 | For instance, to run the tests for the `Polylang` prover: 69 | 70 | ```bash 71 | $ cargo test -p polylang-prover 72 | ``` -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "polylang" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | crate-type = ["staticlib", "cdylib", "rlib"] 10 | 11 | [[bin]] 12 | name = "compile" 13 | path = "src/bin/compile/main.rs" 14 | 15 | [workspace] 16 | members = [ 17 | "parser", 18 | "prover", 19 | "abi", 20 | "miden-run", 21 | "error", 22 | "tests", 23 | "wasm-api", 24 | "server", 25 | "server-routes", 26 | ] 27 | exclude = ["showcase"] 28 | 29 | [features] 30 | default = ["parser", "bindings"] 31 | parser = [] 32 | bindings = [] 33 | 34 | [dependencies] 35 | abi = { path = "./abi" } 36 | error = { path = "./error" } 37 | regex = { version = "1", default-features = false } 38 | wasm-bindgen = "0.2" 39 | console_error_panic_hook = "0.1.7" 40 | serde = { version = "1.0", features = ["derive", "rc"] } 41 | serde_json = { version = "1.0", features = ["arbitrary_precision"] } 42 | polylang_parser = { path = "./parser" } 43 | miden-vm = { git = "https://github.com/0xPolygonMiden/miden-vm", tag = "v0.7.0", default-features = false } 44 | miden-processor = { git = "https://github.com/0xPolygonMiden/miden-vm", tag = "v0.7.0", default-features = false } 45 | miden-stdlib = { git = "https://github.com/0xPolygonMiden/miden-vm", tag = "v0.7.0", default-features = false } 46 | winter-math = "*" 47 | lazy_static = "1.4.0" 48 | base64 = "0.21.0" 49 | derive_more = { version = "0.99.17", features = [ 50 | "deref", 51 | "from", 52 | "deref_mut", 53 | ], default-features = false } 54 | parking_lot = "0.12.1" 55 | 56 | [dev-dependencies] 57 | pretty_assertions = "1.3.0" 58 | expect-test = "1.4.0" 59 | itertools = "0.10.5" 60 | test-case = "3.0.0" 61 | 62 | [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] 63 | rand = "0.8.5" 64 | quickcheck_macros = "1.0.0" 65 | quickcheck = "1.0.3" 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Polybase (Zeplo Inc) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Build 2 | 3 | ### Javascript 4 | 5 | Install wasm-pack: `cargo install wasm-pack` 6 | 7 | ```bash 8 | cd js 9 | yarn build 10 | ``` 11 | 12 | ## Compiling Polylang to Miden 13 | 14 | You can use the `compile` binary to compile Polylang functions to Miden. Compile outputs the generated Miden assembly to stdout, you can pipe it to `miden-run` to run it. 15 | 16 | ### Example of compiling and running a contract function 17 | 18 | ```bash 19 | $ cargo run --bin compile -- contract:Account function:setName <<<'contract Account { id: string; name: string; function setName(newName: string) { this.name = newName; } }' 20 | 21 | $ cargo run --bin compile -- contract:Account function:setName <<<'contract Account { id: string; name: string; function setName(newName: string) { this.name = newName; } }' \ 22 | | cargo run -p miden-run -- \ 23 | --this-json '{ "id": "id1", "name": "John" }' \ 24 | --advice-tape-json '["Tom"]' 25 | 26 | # Output: this_json: {"id":"id1","name":"Tom"} 27 | ``` 28 | 29 | ### Example of compiling and running a standalone function 30 | 31 | ```bash 32 | $ cargo run --bin compile -- function:main <<<'function main() { }' | cargo run -p miden-run 33 | ``` 34 | 35 | ## Test 36 | 37 | ```bash 38 | cargo test && (cd parser && cargo test) 39 | ``` 40 | 41 | ## Contribution 42 | 43 | Contributions of all sorts (bug reports, enhancement requests etc.) are welcome. For more information on contribution tips and guidelines, please see the [Contributing](CONTRIBUTING.md) page. 44 | -------------------------------------------------------------------------------- /abi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "abi" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | error = { path = "../error" } 10 | base64 = "0.21.0" 11 | serde = { version = "1.0", features = ["derive", "rc"] } 12 | serde_json = { version = "1.0", features = ["arbitrary_precision"] } 13 | snafu = "0.7.4" 14 | hex = "0.4" 15 | libsecp256k1 = { version = "0.7.1", default-features = false } 16 | -------------------------------------------------------------------------------- /abi/src/publickey.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use base64::Engine; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use error::prelude::{whatever, Whatever}; 7 | use snafu::ResultExt; 8 | 9 | /// Layout: [key, crv, alg, use, extra_ptr] 10 | /// `extra_ptr` in secp256k1 is pointer to 64 bytes of data, 11 | /// the x and y coordinates of the public key. 12 | pub const WIDTH: u32 = 5; 13 | 14 | // {"alg":"ES256K","crv":"secp256k1","kty":"EC","use":"sig","x":"TOz1M-Y1MVF6i7duA-aWbNSzwgiRngrMFViHOjR3O0w=","y":"XqGeNTl4BoJMANDK160xXhGjpRqy0bHqK_Rn-jsco1o="}d 15 | #[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] 16 | pub enum Kty { 17 | #[default] 18 | EC, 19 | } 20 | 21 | impl From for u8 { 22 | fn from(value: Kty) -> Self { 23 | match value { 24 | Kty::EC => 1, 25 | } 26 | } 27 | } 28 | 29 | impl From for Kty { 30 | fn from(value: u8) -> Self { 31 | match value { 32 | 1 => Kty::EC, 33 | _ => panic!("invalid kty: {}", value), 34 | } 35 | } 36 | } 37 | 38 | impl FromStr for Kty { 39 | type Err = Whatever; 40 | 41 | fn from_str(s: &str) -> Result { 42 | match s { 43 | "EC" => Ok(Kty::EC), 44 | _ => whatever!("invalid kty: {s}"), 45 | } 46 | } 47 | } 48 | 49 | #[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] 50 | #[serde(rename_all = "lowercase")] 51 | pub enum Crv { 52 | #[default] 53 | Secp256k1, 54 | } 55 | 56 | impl From for u8 { 57 | fn from(value: Crv) -> Self { 58 | match value { 59 | Crv::Secp256k1 => 1, 60 | } 61 | } 62 | } 63 | 64 | impl From for Crv { 65 | fn from(value: u8) -> Self { 66 | match value { 67 | 1 => Crv::Secp256k1, 68 | _ => panic!("invalid crv: {}", value), 69 | } 70 | } 71 | } 72 | 73 | impl FromStr for Crv { 74 | type Err = Whatever; 75 | 76 | fn from_str(s: &str) -> Result { 77 | match s { 78 | "secp256k1" => Ok(Crv::Secp256k1), 79 | _ => whatever!("invalid crv: {s}"), 80 | } 81 | } 82 | } 83 | 84 | #[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] 85 | pub enum Alg { 86 | #[default] 87 | ES256K, 88 | } 89 | 90 | impl From for u8 { 91 | fn from(value: Alg) -> Self { 92 | match value { 93 | Alg::ES256K => 1, 94 | } 95 | } 96 | } 97 | 98 | impl From for Alg { 99 | fn from(value: u8) -> Self { 100 | match value { 101 | 1 => Alg::ES256K, 102 | _ => panic!("invalid alg: {}", value), 103 | } 104 | } 105 | } 106 | 107 | impl FromStr for Alg { 108 | type Err = Whatever; 109 | 110 | fn from_str(s: &str) -> Result { 111 | match s { 112 | "ES256K" => Ok(Alg::ES256K), 113 | _ => whatever!("invalid alg: {s}"), 114 | } 115 | } 116 | } 117 | 118 | #[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] 119 | #[serde(rename_all = "lowercase")] 120 | pub enum Use { 121 | #[default] 122 | Sig, 123 | } 124 | 125 | impl From for u8 { 126 | fn from(value: Use) -> Self { 127 | match value { 128 | Use::Sig => 1, 129 | } 130 | } 131 | } 132 | 133 | impl From for Use { 134 | fn from(value: u8) -> Self { 135 | match value { 136 | 1 => Use::Sig, 137 | _ => panic!("invalid use: {}", value), 138 | } 139 | } 140 | } 141 | 142 | impl FromStr for Use { 143 | type Err = Whatever; 144 | 145 | fn from_str(s: &str) -> Result { 146 | match s { 147 | "sig" => Ok(Use::Sig), 148 | _ => whatever!("invalid use: {s}"), 149 | } 150 | } 151 | } 152 | 153 | #[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] 154 | pub struct Key { 155 | pub kty: Kty, 156 | pub crv: Crv, 157 | pub alg: Alg, 158 | #[serde(rename = "use")] 159 | pub use_: Use, 160 | #[serde( 161 | serialize_with = "to_url_safe_base64", 162 | deserialize_with = "from_url_safe_base64" 163 | )] 164 | pub x: [u8; 32], 165 | #[serde( 166 | serialize_with = "to_url_safe_base64", 167 | deserialize_with = "from_url_safe_base64" 168 | )] 169 | pub y: [u8; 32], 170 | } 171 | 172 | fn to_url_safe_base64(bytes: &[u8; 32], serializer: S) -> std::result::Result 173 | where 174 | S: serde::Serializer, 175 | { 176 | serializer.serialize_str(&base64::engine::general_purpose::URL_SAFE.encode(bytes)) 177 | } 178 | 179 | fn from_url_safe_base64<'de, D>(deserializer: D) -> std::result::Result<[u8; 32], D::Error> 180 | where 181 | D: serde::Deserializer<'de>, 182 | { 183 | let s = String::deserialize(deserializer)?; 184 | base64::engine::general_purpose::URL_SAFE 185 | .decode(s.as_bytes()) 186 | .map_err(serde::de::Error::custom)? 187 | .try_into() 188 | .map_err(|_| serde::de::Error::custom("invalid base64")) 189 | } 190 | 191 | impl Key { 192 | pub fn from_secp256k1_bytes(bytes: &[u8]) -> Result { 193 | let mut x = [0; 32]; 194 | let mut y = [0; 32]; 195 | 196 | match bytes.len() { 197 | 65 => { 198 | x.copy_from_slice(&bytes[1..33]); 199 | y.copy_from_slice(&bytes[33..]); 200 | } 201 | 64 => { 202 | x.copy_from_slice(&bytes[..32]); 203 | y.copy_from_slice(&bytes[32..]); 204 | } 205 | 33 => { 206 | let pk = libsecp256k1::PublicKey::parse_compressed(bytes.try_into().unwrap()).map_err(|e| e.to_string()).with_whatever_context(|e| { 207 | format!("invalid secp256k1 public key bytes: {e}") 208 | })?; 209 | 210 | let uncompressed: [u8; 65] = pk.serialize(); 211 | x.copy_from_slice(&uncompressed[1..33]); 212 | y.copy_from_slice(&uncompressed[33..]); 213 | } 214 | 20 => whatever!("you provided an address, where a public key is expected. See https://ethereum.stackexchange.com/questions/13778/get-public-key-of-any-ethereum-account"), 215 | _ => whatever!( 216 | "invalid secp256k1 xy bytes length: {}. A key should be 65, 64 or 33 bytes long.", 217 | bytes.len() 218 | ), 219 | } 220 | 221 | Ok(Key { 222 | kty: Kty::EC, 223 | crv: Crv::Secp256k1, 224 | alg: Alg::ES256K, 225 | use_: Use::Sig, 226 | x, 227 | y, 228 | }) 229 | } 230 | 231 | pub fn to_64_byte_hex(&self) -> String { 232 | format!("0x{}{}", hex::encode(self.x), hex::encode(self.y)) 233 | } 234 | 235 | pub fn to_compressed_33_byte_hex(&self) -> String { 236 | let mut bytes = [0; 33]; 237 | bytes[0] = 0x02 | (self.y[31] & 0x01); 238 | bytes[1..].copy_from_slice(&self.x); 239 | format!("0x{}", hex::encode(bytes)) 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail 4 | 5 | cargo build --release --bin compile 2>/dev/null || { echo "Failed to build compile" && exit 1; } 6 | # cargo build --release --bin miden-run 2>/dev/null || { echo "Failed to build miden-run" && exit 1; } 7 | 8 | declare -A specific_error_counter=() 9 | success_count=0 10 | failure_count=0 11 | 12 | for file in ./test-collections/*; do 13 | # Skip this user's schemas. They have a lot of errors unrelated to the compiler. 14 | if [[ $file =~ ./test-collections/pk-0x86b28d5590a407110f9cac95fd554cd4fc5bd611d6d88aed5fdbeee519f5792411d128cabf54b3035c2bf3f14c50e37c3cfc98523c2243b42cd394da42ca48f8-* ]]; then 15 | echo "Skipping file: $file" >&2 16 | continue 17 | fi 18 | 19 | echo "Processing file: $file" >&2 20 | code=$(cat "$file") 21 | 22 | collection_or_function=$(printf '%s' "$code" | grep -E '^ *(collection|function)' | awk '{ print $1" "$2 }' | sed 's/[(|{|}|)]/ /g' | awk '{ print $1","$2 }') 23 | current_collection="" 24 | for part in $collection_or_function; do 25 | type=$(printf '%s' "$part" | cut -d',' -f1) 26 | name=$(printf '%s' "$part" | cut -d',' -f2) 27 | 28 | case "$type" in 29 | collection) 30 | current_collection="$name" 31 | ;; 32 | function) 33 | output=$(./target/release/compile collection:"$current_collection" function:"$name" <<<"$code" 2>&1) 34 | if [ $? -ne 0 ]; then 35 | if [ -z "${specific_error_counter["$output"]+x}" ]; then 36 | specific_error_counter["$output"]=0 37 | fi 38 | specific_error_counter["$output"]=$((specific_error_counter["$output"] + 1)) 39 | echo "Failed to compile $current_collection:$name - $output" 40 | failure_count=$((failure_count + 1)) 41 | else 42 | success_count=$((success_count + 1)) 43 | fi 44 | ;; 45 | esac 46 | done 47 | done 48 | 49 | echo "Successes: $success_count" 50 | echo "Failures: $failure_count" 51 | 52 | echo "Top errors:" 53 | for error in "${!specific_error_counter[@]}"; do 54 | echo " ${specific_error_counter["$error"]} - $error" 55 | done | sort -nr 56 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1-bookworm AS builder 2 | 3 | WORKDIR /build 4 | 5 | COPY Cargo.lock ./ 6 | COPY Cargo.toml ./ 7 | COPY server ./server/ 8 | COPY server-routes ./server-routes/ 9 | COPY prover ./prover/ 10 | COPY abi ./abi 11 | COPY error ./error/ 12 | COPY miden-run ./miden-run/ 13 | COPY tests ./tests/ 14 | COPY wasm-api ./wasm-api/ 15 | COPY src ./src/ 16 | COPY parser ./parser/ 17 | 18 | RUN cd server && cargo build --release 19 | 20 | # Server 21 | FROM debian:bookworm-slim AS server 22 | 23 | RUN apt-get update && apt-get install -y openssl ca-certificates 24 | 25 | COPY --from=builder /build/target/release/server /usr/local/bin/server 26 | 27 | CMD ["server"] 28 | -------------------------------------------------------------------------------- /error/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "error" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | derive_more = { version = "0.99.17", default-features = false, features = ["deref"] } 10 | parking_lot = "0.12.1" 11 | serde = { version = "1.0.163", features = ["derive"] } 12 | snafu = "0.7.4" 13 | 14 | [dev-dependencies] 15 | test-case = "3.0.0" 16 | -------------------------------------------------------------------------------- /error/src/kind.rs: -------------------------------------------------------------------------------- 1 | use snafu::Snafu; 2 | 3 | #[derive(Snafu, Debug)] 4 | #[snafu(visibility(pub))] 5 | pub enum ErrorKind { 6 | #[snafu(display("{source}"))] 7 | Wrapped { source: Box }, 8 | #[snafu(display("invalid address 0x{addr:x} for {type_name}"))] 9 | InvalidAddress { addr: u64, type_name: &'static str }, 10 | #[snafu(display("cannot parse {type_name} from {input} ({source})"))] 11 | Parse { 12 | type_name: &'static str, 13 | input: String, 14 | source: Box, 15 | }, 16 | #[snafu(display("{type_name} {item} not found"))] 17 | NotFound { 18 | type_name: &'static str, 19 | item: String, 20 | }, 21 | #[snafu(display("type mismatch: {context}"))] 22 | TypeMismatch { context: String }, 23 | #[snafu(display("incorrect number of arguments {found} but expected {expected}"))] 24 | ArgumentsCount { found: usize, expected: usize }, 25 | #[snafu(display( 26 | "stack depth is too small found {stack_len}{}", 27 | if let Some(expected) = expected { 28 | format!(" but expected {expected}") 29 | } else { 30 | "".to_string() 31 | } 32 | ))] 33 | Stack { 34 | stack_len: usize, 35 | expected: Option, 36 | }, 37 | #[snafu(display("{msg}"))] 38 | Simple { msg: String }, 39 | #[snafu(display("{context} >> {source}"))] 40 | Nested { 41 | context: String, 42 | source: Box, 43 | }, 44 | #[snafu(display("i/o error: {source}"))] 45 | Io { source: std::io::Error }, 46 | #[snafu(display("{context} is not implemented yet"))] 47 | NotImplemented { context: String }, 48 | } 49 | -------------------------------------------------------------------------------- /error/src/span.rs: -------------------------------------------------------------------------------- 1 | use parking_lot::Mutex; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Copy)] 5 | pub struct Span { 6 | pub(crate) start: usize, 7 | pub(crate) end: usize, 8 | } 9 | 10 | impl Span { 11 | pub fn new(start: usize, end: usize) -> Self { 12 | Self { start, end } 13 | } 14 | } 15 | 16 | #[macro_export] 17 | macro_rules! maybe_start { 18 | ($span:expr) => { 19 | let _span; 20 | { 21 | let span: Option<_> = $span; 22 | if let Some(span) = span { 23 | _span = $crate::span::start(span); 24 | } 25 | } 26 | }; 27 | } 28 | 29 | static CURRENT_SPAN: Mutex> = Mutex::new(None); 30 | 31 | pub struct SpanGuard(Option); 32 | 33 | #[must_use] 34 | pub fn start(span: impl Into) -> SpanGuard { 35 | let old = std::mem::replace(&mut *CURRENT_SPAN.lock(), Some(span.into())); 36 | SpanGuard(old) 37 | } 38 | 39 | pub(crate) fn get() -> Option { 40 | *CURRENT_SPAN.lock() 41 | } 42 | 43 | impl Drop for SpanGuard { 44 | fn drop(&mut self) { 45 | *CURRENT_SPAN.lock() = self.0; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /js/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "standard" 8 | ], 9 | "parser": "@typescript-eslint/parser", 10 | "parserOptions": { 11 | "ecmaVersion": "latest", 12 | "sourceType": "module" 13 | }, 14 | "plugins": [ 15 | "@typescript-eslint" 16 | ], 17 | "rules": { 18 | "comma-dangle": [ 19 | 2, 20 | "always-multiline" 21 | ], 22 | "object-curly-spacing": [ 23 | 2, 24 | "always" 25 | ], 26 | "react/display-name": 0, 27 | "semi": [ 28 | "error", 29 | "never" 30 | ], 31 | "no-underscore-dangle": 0, 32 | "space-before-function-paren": 0, 33 | "arrow-body-style": 0, 34 | "no-use-before-define": 0, 35 | "arrow-parens": 0, 36 | "no-trailing-spaces": "error", 37 | "@typescript-eslint/indent": [ 38 | "error", 39 | 2 40 | ], 41 | "quotes": "off", 42 | "@typescript-eslint/quotes": [ 43 | "error", 44 | "single" 45 | ] 46 | } 47 | } -------------------------------------------------------------------------------- /js/.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | node_modules 3 | dist 4 | node 5 | web 6 | -------------------------------------------------------------------------------- /js/lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "useNx": true, 4 | "useWorkspaces": false, 5 | "packages": [ 6 | "./" 7 | ], 8 | "version": "0.6.0" 9 | } 10 | -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polybase/polylang", 3 | "version": "0.6.0", 4 | "description": "", 5 | "main": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "license": "MIT", 8 | "files": [ 9 | "pkg/*", 10 | "pkg-thin/*", 11 | "dist/*" 12 | ], 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\"", 15 | "clean": "rimraf node/* && rimraf web/*", 16 | "build": "yarn clean && webpack build", 17 | "prepare": "yarn build", 18 | "fix": "yarn eslint \"./src/**/*.{ts,tsx}\" webpack.config.js --fix" 19 | }, 20 | "devDependencies": { 21 | "@typescript-eslint/eslint-plugin": "^5.25.0", 22 | "@typescript-eslint/parser": "^5.25.0", 23 | "@wasm-tool/wasm-pack-plugin": "^1.6.0", 24 | "eslint": "^8.22.0", 25 | "eslint-config-standard": "^17.0.0", 26 | "eslint-plugin-import": "^2.26.0", 27 | "eslint-plugin-n": "^15.2.0", 28 | "eslint-plugin-promise": "^6.0.0", 29 | "lerna": "^6.5.1", 30 | "rimraf": "^3.0.2", 31 | "ts-loader": "^9.4.0", 32 | "typescript": "^4.6.4", 33 | "webpack": "^5.74.0", 34 | "webpack-cli": "^4.10.0", 35 | "webpack-node-externals": "^3.0.0" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "git+https://github.com/polybase/polylang.git" 40 | }, 41 | "engines": { 42 | "node": ">=16.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /js/src/ast.ts: -------------------------------------------------------------------------------- 1 | export type Root = Node[] 2 | 3 | export type Node = Contract | { kind: string } 4 | 5 | export interface Contract { 6 | kind: 'contract' 7 | namespace: Namespace 8 | name: string 9 | attributes: ContractAttribute[] 10 | } 11 | 12 | export interface Namespace { 13 | kind: 'namespace', 14 | value: string 15 | } 16 | 17 | export type ContractAttribute = Property | Index | Method | Directive 18 | 19 | export interface Property { 20 | kind: 'property', 21 | name: string, 22 | type: Type 23 | directives: Directive[] 24 | } 25 | 26 | export interface Index { 27 | kind: 'index', 28 | fields: IndexField[] 29 | } 30 | 31 | export interface IndexField { 32 | direction: IndexFieldDirection 33 | fieldPath: string[] 34 | } 35 | 36 | export type IndexFieldDirection = 'asc' | 'desc' 37 | 38 | export interface Method { 39 | kind: 'method' 40 | name: string 41 | code: string 42 | attributes: MethodAttribute[] 43 | } 44 | 45 | export type MethodAttribute = Parameter | ReturnValue | Directive 46 | 47 | export interface Parameter { 48 | kind: 'parameter' 49 | name: string 50 | type: Type 51 | required: boolean 52 | directives: Directive[] 53 | } 54 | 55 | export type Type = Primitive | Object | Array | Map | ForeignRecord | PublicKey 56 | 57 | export interface Primitive { 58 | kind: 'primitive', 59 | value: 'string' | 'number' | 'boolean' | 'bytes' 60 | } 61 | 62 | export interface Array { 63 | kind: 'array', 64 | value: Primitive[] 65 | } 66 | 67 | export interface Map { 68 | kind: 'map', 69 | key: Primitive 70 | value: Primitive | ForeignRecord 71 | } 72 | 73 | export interface Object { 74 | kind: 'object', 75 | fields: ObjectField[] 76 | } 77 | 78 | export interface ObjectField { 79 | name: string 80 | type: Type 81 | required: boolean 82 | } 83 | 84 | export interface ForeignRecord { 85 | kind: 'foreignrecord', 86 | contract: string 87 | } 88 | 89 | export interface PublicKey { 90 | kind: 'publickey', 91 | } 92 | 93 | export interface Directive { 94 | kind: 'directive' 95 | name: string 96 | arguments: DirectiveArgument[] 97 | } 98 | 99 | export type DirectiveArgument = FieldReference 100 | 101 | export interface FieldReference { 102 | kind: 'fieldreference', 103 | path: string[] 104 | } 105 | 106 | export interface ReturnValue { 107 | kind: 'returnvalue' 108 | name: string 109 | type: Type 110 | } 111 | -------------------------------------------------------------------------------- /js/src/common.ts: -------------------------------------------------------------------------------- 1 | export interface Result { 2 | Err: { 3 | message: string 4 | } 5 | Ok: T 6 | } 7 | 8 | export function unwrap (value: Result): T { 9 | if (value.Err) { 10 | throw new Error(value.Err.message) 11 | } 12 | 13 | return value.Ok 14 | } 15 | -------------------------------------------------------------------------------- /js/src/index.ts: -------------------------------------------------------------------------------- 1 | import { unwrap } from './common' 2 | export * as AST from './ast' 3 | 4 | const parser = import('../pkg/index.js').then(p => p.default).then(p => { 5 | p.init() 6 | return p 7 | }) 8 | 9 | export interface Program { 10 | nodes: RootNode[] 11 | } 12 | 13 | export interface RootNode { 14 | Contract: Contract 15 | Function: Function 16 | } 17 | 18 | export type Contract = any 19 | export type Function = any 20 | 21 | export async function parse(code: string, namespace: string): Promise<[Program, any]> { 22 | return unwrap(JSON.parse((await parser).parse(code, namespace))) 23 | } 24 | 25 | export interface JSContract { 26 | code: string 27 | } 28 | 29 | export async function generateJSContract(contract: any): Promise { 30 | return unwrap(JSON.parse((await parser).generate_js_contract(JSON.stringify(contract)))) 31 | } 32 | -------------------------------------------------------------------------------- /js/src/validator/index.ts: -------------------------------------------------------------------------------- 1 | import { unwrap } from '../common' 2 | 3 | const parser = import('../../pkg-thin/index.js').then(p => p.default).then(p => { 4 | p.init() 5 | return p 6 | }) 7 | 8 | export async function validateSet(contractAST: any, data: { [k: string]: any }): Promise { 9 | return unwrap(JSON.parse((await parser).validate_set(JSON.stringify(contractAST), JSON.stringify(data)))) 10 | } 11 | -------------------------------------------------------------------------------- /js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 4 | "module": "CommonJS", /* Specify what module code is generated. */ 5 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 6 | "declarationMap": true, /* Create sourcemaps for d.ts files. */ 7 | "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 8 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 9 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 10 | "strict": true, /* Enable all strict type-checking options. */ 11 | "skipLibCheck": true, 12 | "allowJs": true 13 | }, 14 | "include": ["src/**/*"], 15 | } 16 | -------------------------------------------------------------------------------- /js/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const nodeExternals = require('webpack-node-externals') 4 | const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin') 5 | 6 | const inlineWASM = (pkgDir) => ({ 7 | apply: (compiler) => { 8 | compiler.hooks.beforeCompile.tap('InlineWASM', () => { 9 | const wasm = fs.readFileSync( 10 | path.resolve(__dirname, pkgDir + '/index_bg.wasm'), 11 | ) 12 | 13 | const js = ` 14 | import * as index_bg from "./index_bg.js" 15 | 16 | const base64 = "${Buffer.from(wasm).toString('base64')}" 17 | 18 | function toUint8Array (s) { 19 | return new Uint8Array(atob(s).split('').map(c => c.charCodeAt(0))) 20 | } 21 | 22 | const wasm = toUint8Array(base64) 23 | 24 | const { instance } = await WebAssembly.instantiate(wasm, { 25 | "./index_bg.js": index_bg, 26 | }) 27 | 28 | export default instance.exports 29 | ` 30 | 31 | fs.writeFileSync(path.resolve(__dirname, pkgDir + '/index_bg.wasm.js'), js) 32 | 33 | const index = fs.readFileSync( 34 | path.resolve(__dirname, pkgDir + '/index_bg.js'), 35 | ) 36 | 37 | fs.writeFileSync( 38 | path.resolve(__dirname, pkgDir + '/index_bg.js'), 39 | index 40 | .toString() 41 | .replace( 42 | 'import * as wasm from \'./index_bg.wasm\'', 43 | 'import wasm from \'./index_bg.wasm.js\'', 44 | ), 45 | ) 46 | 47 | fs.unlinkSync(path.resolve(__dirname, pkgDir + '/index_bg.wasm')) 48 | }) 49 | }, 50 | }) 51 | 52 | module.exports = { 53 | target: 'web', 54 | entry: { 55 | index: './src/index.ts', 56 | 'validator/index': './src/validator/index.ts', 57 | }, 58 | experiments: { 59 | topLevelAwait: true, 60 | }, 61 | resolve: { 62 | extensions: ['.ts', '.js'], 63 | }, 64 | externals: [nodeExternals(), { buffer: 'buffer' }], 65 | module: { 66 | rules: [ 67 | { 68 | test: /\.ts$/, 69 | loader: 'ts-loader', 70 | options: { 71 | compilerOptions: { 72 | outDir: path.resolve(__dirname, 'dist'), 73 | }, 74 | }, 75 | }, 76 | ], 77 | }, 78 | plugins: [ 79 | new WasmPackPlugin({ 80 | crateDirectory: path.resolve(__dirname, '..'), 81 | outDir: path.resolve(__dirname, 'pkg'), 82 | }), 83 | inlineWASM('pkg'), 84 | new WasmPackPlugin({ 85 | crateDirectory: path.resolve(__dirname, '..'), 86 | outDir: path.resolve(__dirname, 'pkg-thin'), 87 | extraArgs: '-- --no-default-features --features bindings', 88 | }), 89 | inlineWASM('pkg-thin'), 90 | ], 91 | output: { 92 | path: path.resolve(__dirname, 'dist'), 93 | filename: '[name].js', 94 | libraryTarget: 'commonjs2', 95 | }, 96 | } 97 | -------------------------------------------------------------------------------- /miden-run/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "miden-run" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | abi = { path = "../abi" } 10 | polylang-prover = { path = "../prover" } 11 | error = { path = "../error" } 12 | serde = { version = "1.0", features = ["derive", "rc"] } 13 | serde_json = { version = "1.0", features = ["arbitrary_precision"] } 14 | -------------------------------------------------------------------------------- /parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "polylang_parser" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | error = { path = "../error" } 10 | derive_more = { version = "0.99.17", default-features = false, features = ["deref", "deref_mut", "from", "display"] } 11 | lalrpop-util = { version = "0.19.7", features = ["lexer"] } 12 | regex = { version = "1", default-features = false } 13 | serde = { version = "1.0", features = ["derive", "rc"] } 14 | serde_json = "1.0" 15 | hex = "0.4" 16 | 17 | [build-dependencies] 18 | lalrpop = "0.19.7" 19 | -------------------------------------------------------------------------------- /parser/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | lalrpop::process_root().unwrap(); 3 | } 4 | -------------------------------------------------------------------------------- /parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | mod lexer; 3 | 4 | pub use lalrpop_util::ParseError; 5 | pub use lexer::LexicalError; 6 | 7 | pub mod polylang { 8 | #![allow(unused)] 9 | 10 | use lalrpop_util::lalrpop_mod; 11 | lalrpop_mod!( 12 | #[allow(dead_code, clippy::all)] 13 | polylang 14 | ); 15 | pub use polylang::*; 16 | } 17 | 18 | pub fn parse( 19 | input: &str, 20 | ) -> Result> { 21 | let lexer = lexer::Lexer::new(input); 22 | polylang::ProgramParser::new().parse(input, lexer) 23 | } 24 | 25 | pub fn parse_expression( 26 | input: &str, 27 | ) -> Result> { 28 | let lexer = lexer::Lexer::new(input); 29 | polylang::ExpressionParser::new().parse(input, lexer) 30 | } 31 | 32 | // temp for compiler 33 | pub fn parse_function( 34 | input: &str, 35 | ) -> Result> { 36 | let lexer = lexer::Lexer::new(input); 37 | polylang::FunctionParser::new().parse(input, lexer) 38 | } 39 | -------------------------------------------------------------------------------- /prover/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "polylang-prover" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [features] 9 | default = [] 10 | multi-cpu = ["miden-processor/concurrent"] 11 | metal = ["miden-prover/metal"] 12 | 13 | [dependencies] 14 | abi = { path = "../abi" } 15 | error = { path = "../error" } 16 | polylang = { path = "../", default-features = false } 17 | 18 | miden-vm = { git = "https://github.com/0xPolygonMiden/miden-vm", tag = "v0.7.0", default-features = false } 19 | miden-processor = { git = "https://github.com/0xPolygonMiden/miden-vm", tag = "v0.7.0", default-features = false } 20 | miden-stdlib = { git = "https://github.com/0xPolygonMiden/miden-vm", tag = "v0.7.0", default-features = false } 21 | miden-prover = { git = "https://github.com/0xPolygonMiden/miden-vm", tag = "v0.7.0", default-features = false } 22 | 23 | serde_json = { version = "1.0", features = ["arbitrary_precision"] } 24 | -------------------------------------------------------------------------------- /server-routes/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "server-routes" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | polylang-prover = { path = "../prover" } 10 | abi = { path = "../abi" } 11 | serde = { version = "1", features = ["derive"] } 12 | serde_json = "1" 13 | error = { path = "../error" } 14 | tokio = { version = "1" } 15 | base64 = "0.21.4" 16 | -------------------------------------------------------------------------------- /server-routes/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod prove; 2 | -------------------------------------------------------------------------------- /server-routes/src/prove.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use base64::Engine; 4 | use error::prelude::*; 5 | use polylang_prover::{compile_program, Inputs, ProgramExt}; 6 | use serde::Deserialize; 7 | 8 | type OtherRecordsType = HashMap)>>; 9 | 10 | #[derive(Debug, Deserialize)] 11 | #[serde(rename_all = "camelCase")] 12 | pub struct ProveRequest { 13 | pub miden_code: String, 14 | pub abi: abi::Abi, 15 | pub ctx_public_key: Option, 16 | pub this: Option, // this_json 17 | pub this_salts: Option>, 18 | pub args: Vec, 19 | pub other_records: Option, 20 | } 21 | 22 | pub async fn prove(mut req: ProveRequest) -> Result> { 23 | let program = compile_program(&req.abi, &req.miden_code)?; 24 | 25 | let has_this = req.abi.this_type.is_some(); 26 | let this = req.this.clone().unwrap_or(if has_this { 27 | req.abi.default_this_value()?.try_into()? 28 | } else { 29 | serde_json::Value::Null 30 | }); 31 | 32 | if !has_this { 33 | req.abi.this_type = Some(abi::Type::Struct(abi::Struct { 34 | name: "Empty".to_string(), 35 | fields: Vec::new(), 36 | })); 37 | req.abi.this_addr = Some(0); 38 | } 39 | 40 | let this_salts = req.this_salts.unwrap_or( 41 | req.abi 42 | .this_type 43 | .as_ref() 44 | .map(|ty| match ty { 45 | abi::Type::Struct(st) => Ok(st.fields.iter().map(|_| 0).collect()), 46 | _ => Err(Error::simple("this type must be a struct")), 47 | }) 48 | .transpose()? 49 | .unwrap_or(vec![]), 50 | ); 51 | 52 | let inputs = Inputs::new( 53 | req.abi.clone(), 54 | req.ctx_public_key.clone(), 55 | this_salts, 56 | this.clone(), 57 | req.args.clone(), 58 | req.other_records.clone().unwrap_or_default(), 59 | )?; 60 | 61 | let program_info = program.clone().to_program_info_bytes(); 62 | let output = tokio::task::spawn_blocking({ 63 | let inputs = inputs.clone(); 64 | move || polylang_prover::prove(&program, &inputs).map_err(|e| e.to_string()) 65 | }) 66 | .await??; 67 | 68 | let new_this = TryInto::::try_into(output.new_this)?; 69 | let proof_len = output.proof.len(); 70 | let result_hash = output 71 | .run_output 72 | .result_hash(&req.abi) 73 | .map(|h| h.into_iter().map(|x| x.to_string()).collect::>()); 74 | 75 | Ok(serde_json::json!({ 76 | "old": { 77 | "this": this, 78 | "hashes": inputs.this_field_hashes, 79 | }, 80 | "new": { 81 | "selfDestructed": output.run_output.self_destructed()?, 82 | "this": new_this, 83 | "hashes": output.new_hashes.into_iter().map(|h| { 84 | [ 85 | h[0].to_string(), 86 | h[1].to_string(), 87 | h[2].to_string(), 88 | h[3].to_string(), 89 | ] 90 | }).collect::>() 91 | }, 92 | "stack": { 93 | "input": output.input_stack.into_iter().map(|h| h.to_string()).collect::>(), 94 | "output": output.stack.into_iter().map(|h| h.to_string()).collect::>(), 95 | "overflowAddrs": output.overflow_addrs.into_iter().map(|h| h.to_string()).collect::>(), 96 | }, 97 | "result": if req.abi.result_type.is_some() { 98 | serde_json::json!({ 99 | "value": output.run_output.result(&req.abi).map(TryInto::::try_into)??, 100 | "hash": result_hash, 101 | }) 102 | } else { serde_json::Value::Null }, 103 | "programInfo": base64::engine::general_purpose::STANDARD.encode(program_info), 104 | "proof": base64::engine::general_purpose::STANDARD.encode(output.proof), 105 | "debug": { 106 | "logs": output.run_output.logs(), 107 | }, 108 | "cycleCount": output.run_output.cycle_count, 109 | "proofLength": proof_len, // raw unencoded length 110 | "logs": output.run_output.logs(), 111 | "readAuth": output.run_output.read_auth(), 112 | })) 113 | } 114 | -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | actix-web = "4.4.0" 10 | tokio = { version = "1.32.0", features = ["macros", "rt", "rt-multi-thread"] } 11 | server-routes = { path = "../server-routes" } 12 | actix-cors = "0.6.4" 13 | -------------------------------------------------------------------------------- /server/src/main.rs: -------------------------------------------------------------------------------- 1 | use actix_cors::Cors; 2 | use actix_web::{web, App, HttpResponse, HttpServer, Responder}; 3 | 4 | async fn prove( 5 | req: web::Json, 6 | ) -> Result> { 7 | Ok(HttpResponse::Ok().json(server_routes::prove::prove(req.into_inner()).await?)) 8 | } 9 | 10 | #[tokio::main] 11 | async fn main() { 12 | let port = std::env::var("PORT").unwrap_or("8080".to_string()); 13 | let listen_addr = std::env::var("PROVER_LADDR").unwrap_or(format!("0.0.0.0:{port}")); 14 | 15 | let app = || { 16 | let cors = Cors::permissive(); 17 | App::new() 18 | .wrap(cors) 19 | .service(web::resource("/prove").route(web::post().to(prove))) 20 | }; 21 | 22 | eprintln!("Listening on {}", listen_addr); 23 | 24 | HttpServer::new(app) 25 | .bind(listen_addr) 26 | .unwrap() 27 | .run() 28 | .await 29 | .unwrap(); 30 | } 31 | -------------------------------------------------------------------------------- /showcase/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "polylang-examples" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | polylang= { git = "https://github.com/polybase/polylang", branch = "main" } 8 | polylang_parser = { git = "https://github.com/polybase/polylang", branch = "main" } 9 | polylang-prover = { git = "https://github.com/polybase/polylang" , branch = "main"} 10 | abi = { git = "https://github.com/polybase/polylang" , branch = "main"} 11 | serde_json = { version = "1.0.107", features = ["arbitrary_precision" ]} 12 | serde = { version = "1.0.188", features = ["derive"] } 13 | -------------------------------------------------------------------------------- /showcase/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Polybase Labs (https://polybaselabs.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /showcase/README.md: -------------------------------------------------------------------------------- 1 | # Polylang Showcase 2 | 3 | These are some sample applications demonstrating `Polylang`'s features. 4 | 5 | 6 | ## Run 7 | 8 | All examples are part of the same `Cargo` package. To run a specific example, pass in the example name: 9 | 10 | ```bash 11 | $ cargo run --release --example 12 | ``` 13 | 14 | For instance: 15 | 16 | ```bash 17 | $ cargo run --release --example hello_world 18 | ``` 19 | 20 | Output: 21 | 22 | ```bash 23 | $ cargo run --release --example hello_world 24 | this_json: {} 25 | result_json: 3 26 | Proof saved to add.proof 27 | ``` 28 | 29 | A demo run of each example is provided as well. 30 | 31 | 32 | ## Examples 33 | 34 | ### Hello, World 35 | 36 | This introductory example simply runs the code for the following contract: 37 | 38 | ```typescript 39 | @public 40 | contract HelloWorld { 41 | sum: i32; 42 | 43 | function add(a: i32, b: i32) { 44 | this.sum = a + b; 45 | } 46 | } 47 | ``` 48 | 49 | The contract provides a function `add`, which takes in two integers, and adds their values, storing the result in the field `sum`. 50 | 51 | Demo: 52 | 53 | ```bash 54 | $ cargo run --release --example hello_world 55 | this_json: {} 56 | result_json: 3 57 | Proof saved to add.proof 58 | 59 | ``` 60 | 61 | ### Fibonacci 62 | 63 | ```typescript 64 | @public 65 | contract Fibonacci { 66 | fibVal: u32; 67 | 68 | function main(p: u32, a: u32, b: u32) { 69 | for (let i: u32 = 0; i < p; i++) { 70 | let c = a.wrappingAdd(b); 71 | a = b; 72 | b = c; 73 | } 74 | 75 | this.fibVal = a; 76 | } 77 | } 78 | ``` 79 | 80 | The contract provides a function `main`, which calculates the `p`th Fibonacci number, starting with base values 1 and 1. 81 | 82 | Demo: 83 | 84 | ```bash 85 | $ cargo run --release --example fibonacci 86 | this_json: {} 87 | result_json: 34 88 | Proof saved to fibonacci.proof 89 | ``` 90 | 91 | ### Reversing an Array 92 | 93 | ```typescript 94 | @public 95 | contract ReverseArray { 96 | elements: number[]; 97 | 98 | constructor (elements: number[]) { 99 | this.elements = elements; 100 | } 101 | 102 | reverse() { 103 | let reversed: u32[] = []; 104 | let i: u32 = 0; 105 | let one: u32 = 1; 106 | let len: u32 = this.elements.length; 107 | 108 | while (i < len) { 109 | let idx: u32 = len - i - one; 110 | reversed.push(this.elements[idx]); 111 | i = i + one; 112 | } 113 | 114 | this.elements = reversed; 115 | } 116 | } 117 | ``` 118 | 119 | The contract provides a function `reverse` which reverses the numbers in the array `elements`. 120 | 121 | Demo: 122 | 123 | ```bash 124 | $ cargo run --release --example reverse_array 125 | this_json: {"elements":[1,3,4,5,7,6,2,3]} 126 | result_json: [3,2,6,7,5,4,3,1] 127 | Proof saved to reverse.proof 128 | ``` 129 | 130 | ### Binary Search 131 | 132 | ```typescript 133 | contract BinarySearch { 134 | arr: i32[]; 135 | found: boolean; 136 | foundPos: u32; 137 | 138 | constructor (arr: i32[]) { 139 | this.arr = arr; 140 | } 141 | 142 | function search(elem: i32) { 143 | let low: u32 = 0; 144 | let high: u32 = this.arr.length; 145 | let one: u32 = 1; 146 | let two: u32 = 2; 147 | 148 | while (low <= high) { 149 | let mid: u32 = low + high; 150 | mid = mid / two; 151 | 152 | if (this.arr[mid] < elem) { 153 | low = mid + one; 154 | } else { 155 | if (this.arr[mid] > elem) { 156 | high = mid - one; 157 | } else { 158 | this.found = true; 159 | this.foundPos = mid; 160 | break; 161 | } 162 | } 163 | } 164 | 165 | if (low > high) { 166 | this.found = false; 167 | } 168 | } 169 | } 170 | 171 | ``` 172 | 173 | The example showcases iterative Binary Search. 174 | 175 | Demo: 176 | 177 | ```bash 178 | $ cargo run --release --example binary_search 179 | this_json: {"arr":[1,2,3,3,5,6,11],"found":false,"foundPos":0} 180 | this_json: {"arr":[1,2,3,3,5,6,11],"found":true,"foundPos":4} 181 | ``` 182 | 183 | ### City and Country 184 | 185 | ```typescript 186 | contract City { 187 | id: string; 188 | name: string; 189 | country: Country; 190 | 191 | constructor(id: string, name: string, country: Country) { 192 | this.id = id; 193 | this.name = name; 194 | this.country = country; 195 | } 196 | } 197 | 198 | contract Country { 199 | id: string; 200 | name: string; 201 | 202 | constructor (id: string, name: string) { 203 | this.id = id; 204 | this.name = name; 205 | } 206 | } 207 | 208 | ``` 209 | We have a contract `City` which has a field, `country` of type `Country` (which is itself a contract). This example showcases how we can cross-reference contracts by creating an instance of 210 | `City` with a reference to an instance of `Country`. 211 | 212 | Demo: 213 | 214 | ```bash 215 | $ cargo run --release --example city_country 216 | this_json: {"country":{"id":"usa"},"id":"boston","name":"BOSTON"} 217 | Proof saved to city_country.proof 218 | ``` 219 | 220 | ### Accounts and Balances 221 | 222 | ```typescript 223 | contract Account { 224 | id: string; 225 | balance: number; 226 | 227 | constructor(id: string, balance: number) { 228 | this.id = id; 229 | this.balance = balance; 230 | } 231 | 232 | deposit(amt: number) { 233 | this.balance = this.balance + amt; 234 | } 235 | 236 | withdraw(amt: number) { 237 | if (this.balance < 0) { 238 | error("Insufficient balance"); 239 | } 240 | this.balance = this.balance - amt; 241 | } 242 | 243 | getBalance(): number { 244 | return this.balance; 245 | } 246 | } 247 | ``` 248 | 249 | This example demonstrates a simple account with facilities to deposit, withdraw, and report the balance. 250 | 251 | Demo: 252 | 253 | ```bash 254 | $ cargo run --release --example accounts 255 | this_json: {"balance":100,"id":"id1"} 256 | this_json: {"balance":150,"id":""} 257 | this_json: {"balance":125,"id":""} 258 | this_json: {"balance":125,"id":""} 259 | result_json: 125 260 | 261 | ``` 262 | 263 | 264 | ## Licensing 265 | 266 | All examples are licensed under the [MIT License](LICENSE.md). 267 | 268 | -------------------------------------------------------------------------------- /showcase/examples/accounts.rs: -------------------------------------------------------------------------------- 1 | use polylang_examples::{compile_contract, run_contract, Args, Ctx}; 2 | use serde_json::json; 3 | use std::collections::HashMap; 4 | 5 | const CONTRACT: &str = r#" 6 | contract Account { 7 | id: string; 8 | balance: number; 9 | 10 | constructor(id: string, balance: number) { 11 | this.id = id; 12 | this.balance = balance; 13 | } 14 | 15 | deposit(amt: number) { 16 | this.balance = this.balance + amt; 17 | } 18 | 19 | withdraw(amt: number) { 20 | if (this.balance < 0) { 21 | error("Insufficient balance"); 22 | } 23 | this.balance = this.balance - amt; 24 | } 25 | 26 | getBalance(): number { 27 | return this.balance; 28 | } 29 | } 30 | "#; 31 | 32 | fn main() -> Result<(), Box> { 33 | // initialise 34 | run( 35 | json!({"id": "", "balance": 0}), 36 | "constructor", 37 | Some("[\"id1\", 100]".to_string()), 38 | )?; 39 | 40 | deposit(json!({"id": "id1", "balance": 100}), 50)?; 41 | withdraw(json!( {"id": "id1", "balance": 150}), 25)?; 42 | get_balance(json!( {"id": "id1", "balance": 125}))?; 43 | 44 | Ok(()) 45 | } 46 | 47 | fn deposit(this_json: serde_json::Value, amt: u32) -> Result<(), Box> { 48 | run(this_json, "deposit", Some(format!("[{amt}]")))?; 49 | 50 | Ok(()) 51 | } 52 | 53 | fn withdraw(this_json: serde_json::Value, amt: u32) -> Result<(), Box> { 54 | run(this_json, "withdraw", Some(format!("[{amt}]")))?; 55 | 56 | Ok(()) 57 | } 58 | 59 | fn get_balance(this_json: serde_json::Value) -> Result<(), Box> { 60 | run(this_json, "getBalance", None)?; 61 | 62 | Ok(()) 63 | } 64 | 65 | fn run( 66 | this_json: serde_json::Value, 67 | function_name: &str, 68 | advice: Option, 69 | ) -> Result<(), Box> { 70 | let contract_name = Some("Account"); 71 | let function_name = function_name.to_string(); 72 | 73 | let (miden_code, abi) = compile_contract(CONTRACT, contract_name, &function_name)?; 74 | 75 | let args = Args { 76 | advice_tape_json: advice, 77 | this_values: HashMap::new(), 78 | this_json: Some(this_json), 79 | other_records: HashMap::new(), 80 | abi, 81 | ctx: Ctx::default(), 82 | proof_output: None, // don't generate a proof 83 | }; 84 | 85 | run_contract(miden_code, args)?; 86 | 87 | Ok(()) 88 | } 89 | -------------------------------------------------------------------------------- /showcase/examples/binary_search.rs: -------------------------------------------------------------------------------- 1 | use polylang_examples::{compile_contract, run_contract, Args, Ctx}; 2 | use serde_json::json; 3 | use std::collections::HashMap; 4 | 5 | const CONTRACT: &str = r#" 6 | contract BinarySearch { 7 | arr: i32[]; 8 | found: boolean; 9 | foundPos: u32; 10 | 11 | constructor (arr: i32[]) { 12 | this.arr = arr; 13 | } 14 | 15 | function search(elem: i32) { 16 | let low: u32 = 0; 17 | let high: u32 = this.arr.length; 18 | let one: u32 = 1; 19 | let two: u32 = 2; 20 | 21 | while (low <= high) { 22 | let mid: u32 = low + high; 23 | mid = mid / two; 24 | 25 | if (this.arr[mid] < elem) { 26 | low = mid + one; 27 | } else { 28 | if (this.arr[mid] > elem) { 29 | high = mid - one; 30 | } else { 31 | this.found = true; 32 | this.foundPos = mid; 33 | break; 34 | } 35 | } 36 | } 37 | 38 | if (low > high) { 39 | this.found = false; 40 | } 41 | } 42 | } 43 | "#; 44 | 45 | fn main() -> Result<(), Box> { 46 | let elems = vec![1, 2, 3, 3, 5, 6, 11]; 47 | binary_search(&elems, 15)?; 48 | binary_search(&elems, 5)?; 49 | 50 | Ok(()) 51 | } 52 | 53 | fn binary_search(arr: &Vec, elem: i32) -> Result<(), Box> { 54 | let contract_name = Some("BinarySearch"); 55 | let function_name = "search".to_string(); 56 | 57 | let (miden_code, abi) = compile_contract(CONTRACT, contract_name, &function_name)?; 58 | 59 | let args = Args { 60 | advice_tape_json: Some(format!("[{elem}]")), 61 | this_values: HashMap::new(), 62 | this_json: Some(json!({"arr": arr, "found": false, "foundPos": 0 })), 63 | other_records: HashMap::new(), 64 | abi, 65 | ctx: Ctx::default(), 66 | proof_output: None, 67 | }; 68 | 69 | run_contract(miden_code, args)?; 70 | 71 | Ok(()) 72 | } 73 | -------------------------------------------------------------------------------- /showcase/examples/city_country.rs: -------------------------------------------------------------------------------- 1 | use polylang_examples::{compile_contract, run_contract, Args, Ctx}; 2 | use serde_json::json; 3 | use std::collections::HashMap; 4 | 5 | fn main() -> Result<(), Box> { 6 | // specify your cpntract here 7 | let contract = r#" 8 | contract City { 9 | id: string; 10 | name: string; 11 | country: Country; 12 | 13 | constructor(id: string, name: string, country: Country) { 14 | this.id = id; 15 | this.name = name; 16 | this.country = country; 17 | } 18 | } 19 | 20 | contract Country { 21 | id: string; 22 | name: string; 23 | 24 | constructor (id: string, name: string) { 25 | this.id = id; 26 | this.name = name; 27 | } 28 | } 29 | "#; 30 | 31 | let contract_name = Some("City"); 32 | let function_name = "constructor".to_string(); 33 | let proof_file_name = "city_country.proof"; 34 | 35 | let (miden_code, abi) = compile_contract(contract, contract_name, &function_name)?; 36 | 37 | let args = Args { 38 | advice_tape_json: Some( 39 | "[\"boston\", \"BOSTON\", {\"id\": \"usa\", \"name\": \"USA\" }]".to_string(), 40 | ), 41 | this_values: HashMap::new(), 42 | this_json: Some(json!({"id": "", "name": "", "country": { "id": "", "name": "" }})), 43 | other_records: HashMap::new(), 44 | abi, 45 | ctx: Ctx::default(), 46 | proof_output: Some(proof_file_name.to_string()), 47 | }; 48 | 49 | run_contract(miden_code, args)?; 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /showcase/examples/fibonacci.rs: -------------------------------------------------------------------------------- 1 | use polylang_examples::{compile_contract, run_contract, Args, Ctx}; 2 | use serde_json::json; 3 | use std::collections::HashMap; 4 | 5 | fn main() -> Result<(), Box> { 6 | // specify your cpntract here 7 | let contract = r#" 8 | contract Fibonacci { 9 | function main(p: u32, a: u32, b: u32): u32 { 10 | for (let i: u32 = 0; i < p; i++) { 11 | let c = a.wrappingAdd(b); 12 | a = b; 13 | b = c; 14 | } 15 | 16 | return a; 17 | } 18 | } 19 | "#; 20 | 21 | // pass the name of `contract` here 22 | let contract_name = Some("Fibonacci"); 23 | // pass the name of the function to be executed here 24 | let function_name = "main".to_string(); 25 | // pass the name of the proof file here 26 | let proof_file_name = "fibonacci.proof"; 27 | 28 | let (miden_code, abi) = compile_contract(contract, contract_name, &function_name)?; 29 | 30 | let args = Args { 31 | advice_tape_json: Some("[8, 1, 1]".to_string()), 32 | this_values: HashMap::new(), 33 | this_json: Some(json!([])), 34 | other_records: HashMap::new(), 35 | abi, 36 | ctx: Ctx::default(), 37 | proof_output: Some(proof_file_name.to_string()), 38 | }; 39 | 40 | // Run the contract. In addition to the output (if any), you should see the proof file 41 | // generated in the same directpry: `.proof`. 42 | run_contract(miden_code, args)?; 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /showcase/examples/hello_world.rs: -------------------------------------------------------------------------------- 1 | use polylang_examples::{compile_contract, run_contract, Args, Ctx}; 2 | use serde_json::json; 3 | use std::collections::HashMap; 4 | 5 | fn main() -> Result<(), Box> { 6 | // specify your cpntract here 7 | let contract = r#" 8 | contract HelloWorld { 9 | function add(a: i32, b: i32): i32 { 10 | return a + b; 11 | } 12 | } 13 | "#; 14 | 15 | // pass the name of `contract` here 16 | let contract_name = Some("HelloWorld"); 17 | // pass the name of the function to be executed here 18 | let function_name = "add".to_string(); 19 | // pass the name of the proof file here 20 | let proof_file_name = "add.proof"; 21 | 22 | let (miden_code, abi) = compile_contract(contract, contract_name, &function_name)?; 23 | 24 | let args = Args { 25 | advice_tape_json: Some("[1, 2]".into()), 26 | this_values: HashMap::new(), 27 | this_json: Some(json!({})), 28 | other_records: HashMap::new(), 29 | abi, 30 | ctx: Ctx::default(), 31 | proof_output: Some(proof_file_name.to_string()), 32 | }; 33 | 34 | // Run the contract. In addition to the output (if any), you should see the proof file 35 | // generated in the same directpry: `.proof`. 36 | run_contract(miden_code, args)?; 37 | 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /showcase/examples/reverse_array.rs: -------------------------------------------------------------------------------- 1 | use polylang_examples::{compile_contract, run_contract, Args, Ctx}; 2 | use serde_json::json; 3 | use std::collections::HashMap; 4 | 5 | fn main() -> Result<(), Box> { 6 | // specify your cpntract here 7 | let contract = r#" 8 | contract ReverseArray { 9 | elements: number[]; 10 | 11 | constructor (elements: number[]) { 12 | this.elements = elements; 13 | } 14 | 15 | function reverse(): number[] { 16 | let reversed: u32[] = []; 17 | let i: u32 = 0; 18 | let one: u32 = 1; 19 | let len: u32 = this.elements.length; 20 | 21 | while (i < len) { 22 | let idx: u32 = len - i - one; 23 | reversed.push(this.elements[idx]); 24 | i = i + one; 25 | } 26 | 27 | return reversed; 28 | } 29 | } 30 | "#; 31 | 32 | // pass the name of `contract` here 33 | let contract_name = Some("ReverseArray"); 34 | // pass the name of the function to be executed here 35 | let function_name = "reverse".to_string(); 36 | // pass the name of the proof file here 37 | let proof_file_name = "reverse.proof"; 38 | 39 | let (miden_code, abi) = compile_contract(contract, contract_name, &function_name)?; 40 | 41 | let args = Args { 42 | advice_tape_json: None, 43 | this_values: HashMap::new(), 44 | this_json: Some(json!({"elements": [1, 3, 4, 5, 7, 6, 2, 3]})), 45 | other_records: HashMap::new(), 46 | abi, 47 | ctx: Ctx::default(), 48 | proof_output: Some(proof_file_name.to_string()), 49 | }; 50 | 51 | // Run the contract. In addition to the output (if any), you should see the proof file 52 | // generated in the same directpry: `.proof`. 53 | run_contract(miden_code, args)?; 54 | 55 | Ok(()) 56 | } 57 | -------------------------------------------------------------------------------- /showcase/src/lib.rs: -------------------------------------------------------------------------------- 1 | use abi::Abi; 2 | use std::{collections::HashMap, io::Write}; 3 | 4 | #[derive(Default, serde::Deserialize)] 5 | #[serde(rename_all = "camelCase")] 6 | pub struct Ctx { 7 | pub public_key: Option, 8 | } 9 | 10 | pub struct Args { 11 | pub advice_tape_json: Option, 12 | pub this_values: HashMap, 13 | pub this_json: Option, 14 | pub other_records: HashMap)>>, 15 | pub abi: Abi, 16 | pub ctx: Ctx, 17 | pub proof_output: Option, 18 | } 19 | 20 | impl Args { 21 | pub fn inputs( 22 | &self, 23 | hasher: impl Fn( 24 | abi::Type, 25 | &abi::Value, 26 | Option<&[u32]>, 27 | ) -> Result<[u64; 4], Box>, 28 | ) -> Result> { 29 | let this = self.this_value()?; 30 | let abi::Value::StructValue(sv) = &this else { 31 | return Err("This value is not a struct".into()); 32 | }; 33 | let this_fields = match self.abi.this_type.as_ref().unwrap() { 34 | abi::Type::Struct(s) => &s.fields, 35 | _ => unreachable!(), 36 | }; 37 | let this_field_hashes = sv 38 | .iter() 39 | .enumerate() 40 | .map(|(i, (_, v))| hasher(this_fields[i].1.clone(), &v, Some(&[0]))) 41 | .collect::, _>>()?; 42 | 43 | Ok(polylang_prover::Inputs { 44 | abi: self.abi.clone(), 45 | ctx_public_key: self.ctx.public_key.clone(), 46 | this_salts: sv.iter().map(|_| 0).collect(), 47 | this: this.try_into()?, 48 | this_field_hashes, 49 | args: serde_json::from_str( 50 | &self 51 | .advice_tape_json 52 | .as_ref() 53 | .map(|x| x.as_str()) 54 | .unwrap_or("[]"), 55 | )?, 56 | other_records: self.other_records.clone(), 57 | }) 58 | } 59 | 60 | fn this_value(&self) -> Result> { 61 | self.this_value_json() 62 | } 63 | 64 | fn this_value_json(&self) -> Result> { 65 | let Some(this_json) = &self.this_json else { 66 | return Err("No JSON value for `this`".into()); 67 | }; 68 | 69 | let this_type = self 70 | .abi 71 | .this_type 72 | .as_ref() 73 | .ok_or_else(|| "ABI does not specify a `this` type")?; 74 | 75 | let abi::Type::Struct(struct_) = this_type else { 76 | return Err("This type is not a struct".into()); 77 | }; 78 | 79 | let use_defaults = this_json.as_object().map(|o| o.is_empty()).unwrap_or(false); 80 | 81 | let mut struct_values = Vec::new(); 82 | for (field_name, field_type) in &struct_.fields { 83 | let field_value = match this_json.get(field_name) { 84 | Some(value) => abi::Parser::parse(field_type, value)?, 85 | None if use_defaults => field_type.default_value(), 86 | None if matches!(field_type, abi::Type::Nullable(_)) => field_type.default_value(), 87 | None => return Err(format!("missing value for field `{}`", field_name).into()), 88 | }; 89 | 90 | struct_values.push((field_name.clone(), field_value)); 91 | } 92 | 93 | Ok(abi::Value::StructValue(struct_values)) 94 | } 95 | } 96 | 97 | pub fn compile_contract( 98 | contract: &'static str, 99 | contract_name: Option<&str>, 100 | function_name: &str, 101 | ) -> Result<(String, abi::Abi), Box> { 102 | let program = polylang_parser::parse(&contract)?; 103 | 104 | Ok( 105 | polylang::compiler::compile(program, contract_name, &function_name) 106 | .map_err(|e| e.add_source(contract)) 107 | .unwrap_or_else(|e| panic!("{e}")), 108 | ) 109 | } 110 | 111 | pub fn run_contract(miden_code: String, mut args: Args) -> Result<(), Box> { 112 | let has_this_type = if args.abi.this_type.is_none() { 113 | args.abi.this_type = Some(abi::Type::Struct(abi::Struct { 114 | name: "Empty".to_string(), 115 | fields: Vec::new(), 116 | })); 117 | 118 | false 119 | } else { 120 | true 121 | }; 122 | 123 | let inputs = args.inputs(|t, v, s| Ok(polylang_prover::hash_this(t, v, s)?))?; 124 | 125 | let program = polylang_prover::compile_program(&args.abi, &miden_code) 126 | .map_err(|e| e.add_source(miden_code))?; 127 | 128 | let (output, prove) = polylang_prover::run(&program, &inputs)?; 129 | 130 | if has_this_type { 131 | println!( 132 | "this_json: {}", 133 | TryInto::::try_into(output.this(&args.abi)?)? 134 | ); 135 | } 136 | 137 | if args.abi.result_type.is_some() { 138 | println!( 139 | "result_json: {}", 140 | TryInto::::try_into(output.result(&args.abi)?)? 141 | ); 142 | } 143 | 144 | if let Some(out) = args.proof_output { 145 | let proof = prove()?; 146 | let mut file = std::fs::File::create(&out)?; 147 | file.write_all(&proof.to_bytes())?; 148 | 149 | println!("Proof saved to {out}"); 150 | } 151 | 152 | Ok(()) 153 | } 154 | -------------------------------------------------------------------------------- /site/.env.local.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_PROVER_URL=http://localhost:8080/prove -------------------------------------------------------------------------------- /site/.github/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polybase/polylang/5257cb206079fbbd2e7244359895eac9bd300e1b/site/.github/screenshot.png -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | .env*.local 4 | -------------------------------------------------------------------------------- /site/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Polybase Labs (https://polybaselabs.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /site/README.md: -------------------------------------------------------------------------------- 1 | # Polylang 2 | 3 | This is the source code for the `Polylang` documentation site at [polylang-site](https://polylang.dev/). 4 | 5 | ## Starting local Prover 6 | 7 | For proof verification, the playground needs to communicate with the `prover` server. 8 | 9 | First, run the `prover` as a local instance (the `server` package) from the project root (`polylang`): 10 | 11 | ```bash 12 | $ cargo run -p server 13 | ``` 14 | 15 | Navigate to the site root (`polylang/site`): 16 | 17 | ```bash 18 | $ cd site 19 | ``` 20 | 21 | All subsequent commands wil be executed inside this directory. 22 | 23 | Copy the `.env.local.example` file to `.env.local` (this is a one-time operation). 24 | 25 | ```bash 26 | $ cp .env.local.example .env.local 27 | ``` 28 | 29 | Confirm the contents: 30 | 31 | ```bash 32 | $ more .env.local 33 | NEXT_PUBLIC_PROVER_URL=http://localhost:8080/prove 34 | ``` 35 | 36 | The `NEXT_PUBLIC_PROVER_URL` is the URL of the `prover` instance. If you're hosting it on a different machine (or port), adjust values accordingly. 37 | 38 | ## Build and Run 39 | 40 | Install the dependencies: 41 | 42 | ```bash 43 | $ yarn install 44 | ``` 45 | 46 | Build the project (optimized build): 47 | 48 | ```bash 49 | $ yarn build 50 | ``` 51 | 52 | Start the server: 53 | 54 | ```bash 55 | $ yarn start 56 | ``` 57 | 58 | ### Development 59 | 60 | ```bash 61 | $ yarn dev 62 | ``` 63 | 64 | This will spin up the local development server on [localhost:3000](localhost:3000). 65 | 66 | ## Contribution 67 | 68 | `Polylang` is Free and Open Source Software. We welcome bug reports and patches from everyone. 69 | 70 | For more information on contribution tips and guidelines, please see the [Contributing](https://github.com/polybase/polylang/blob/main/CONTRIBUTING.md) page. 71 | 72 | Note: If you wish to report issues with the `Polylang` documentation (this site), feel free to [open an issue](https://github.com/polybase/polylang/issues). 73 | 74 | ## LICENSE 75 | 76 | This repository is licensed under the [MIT License](LICENSE.md). -------------------------------------------------------------------------------- /site/components/Code.tsx: -------------------------------------------------------------------------------- 1 | import CodeMirror, { ReactCodeMirrorProps } from '@uiw/react-codemirror' 2 | import { ViewUpdate } from '@codemirror/view' 3 | import { githubLight } from '@uiw/codemirror-theme-github' 4 | import { vscodeDark } from '@uiw/codemirror-theme-vscode' 5 | import { useTheme } from 'nextra-theme-docs' 6 | import { javascript } from '@polybase/codemirror-lang-javascript' 7 | import { json } from '@codemirror/lang-json' 8 | 9 | export interface CodeProps extends ReactCodeMirrorProps { 10 | type: 'json' | 'polylang' 11 | value: string, 12 | onChange?: (value: string, update: ViewUpdate) => void 13 | } 14 | 15 | export function Code({ value, onChange, type, ...props }: CodeProps) { 16 | const theme = useTheme() 17 | 18 | return ( 19 | 34 | ) 35 | } -------------------------------------------------------------------------------- /site/components/ColorModeSwitcher.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { 3 | useColorMode, 4 | useColorModeValue, 5 | IconButton, 6 | IconButtonProps, 7 | } from '@chakra-ui/react' 8 | import { useTheme } from 'nextra-theme-docs' 9 | import { FaMoon, FaSun } from 'react-icons/fa' 10 | 11 | type ColorModeSwitcherProps = Omit 12 | 13 | export const ColorModeSwitcher: React.FC = (props) => { 14 | const theme = useTheme() 15 | const { setColorMode } = useColorMode() 16 | const text = useColorModeValue('dark', 'light') 17 | const SwitchIcon = useColorModeValue(FaMoon, FaSun) 18 | 19 | return ( 20 | { 28 | theme.setTheme(theme.resolvedTheme === 'dark' ? 'light' : 'dark') 29 | setColorMode(theme.resolvedTheme === 'dark' ? 'light' : 'dark') 30 | }} 31 | icon={} 32 | aria-label={`Switch to ${text} mode`} 33 | {...props} 34 | /> 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /site/components/ColorModeSync.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useColorMode } from '@chakra-ui/react' 3 | import { useTheme } from 'nextra-theme-docs' 4 | 5 | export function ColorModeSync() { 6 | const { colorMode, setColorMode } = useColorMode() 7 | const theme = useTheme() 8 | 9 | useEffect(() => { 10 | if (theme.resolvedTheme && colorMode !== theme.resolvedTheme) { 11 | setColorMode(theme.resolvedTheme) 12 | } 13 | }, [colorMode, theme.resolvedTheme, setColorMode]) 14 | 15 | return null 16 | } -------------------------------------------------------------------------------- /site/components/Home.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { Button, Link, HStack, Box, Stack, Heading } from '@chakra-ui/react' 4 | import { useTheme } from 'nextra-theme-docs' 5 | import Prover from './Prover' 6 | import Why from './Why' 7 | import Navbar from './Navbar' 8 | import { ColorModeSync } from './ColorModeSync' 9 | 10 | const Home = ({ children }) => { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | TypeScript for
Zero Knowledge 22 |
23 | Provable computation and zero knowledge language. 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | Try it out 33 | 34 | 35 | 36 | 37 | FAQ 38 | {children} 39 | 40 |
41 |
42 |
43 | ) 44 | } 45 | 46 | export default Home -------------------------------------------------------------------------------- /site/components/Logo.tsx: -------------------------------------------------------------------------------- 1 | import { HStack, Heading } from '@chakra-ui/react' 2 | import Image from 'next/image' 3 | 4 | export function Logo({ fontSize = '3xl', size = 50 }) { 5 | return ( 6 | 7 | Polylang 8 | 9 | Polylang 10 | 11 | 12 | ) 13 | } -------------------------------------------------------------------------------- /site/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import Image from 'next/image' 4 | import { Flex, HStack, Heading, Link, Spacer } from '@chakra-ui/react' 5 | import { PoweredBy } from './PoweredBy' 6 | import { Logo } from './Logo' 7 | import { ColorModeSwitcher } from './ColorModeSwitcher' 8 | 9 | const Navbar = () => { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | Playground 17 | Docs 18 | Github 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | export default Navbar -------------------------------------------------------------------------------- /site/components/PoweredBy.tsx: -------------------------------------------------------------------------------- 1 | import { useColorMode } from '@chakra-ui/react' 2 | import Image from 'next/image' 3 | import dark from '../img/made-by/dark.png' 4 | import light from '../img/made-by/light.png' 5 | 6 | export function PoweredBy() { 7 | const { colorMode } = useColorMode() 8 | return ( 9 | 10 | Made by Polybase Labs 11 | 12 | ) 13 | } 14 | 15 | // 16 | // 17 | // logo 18 | // 19 | // 20 | // made by 21 | // polybaselabs 22 | // 23 | // -------------------------------------------------------------------------------- /site/components/Prover.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { Box, Button, Stack, Heading, SimpleGrid, useToast } from '@chakra-ui/react' 3 | import { encodeBase64 } from 'tweetnacl-util' 4 | import { Code } from './Code' 5 | import { EXAMPLES } from './example' 6 | import { run, Output, verify as verifyProof } from './polylang' 7 | import { useAsyncCallback } from './useAsyncCallback' 8 | 9 | interface UserOutput { 10 | // proof: 11 | } 12 | 13 | const Prover = () => { 14 | const [code, setCode] = useState(EXAMPLES[0].code) 15 | const [inputs, setInputs] = useState(EXAMPLES[0].inputs) 16 | const [report, setReport] = useState('') 17 | const [output, setOutput] = useState(null) 18 | const toast = useToast() 19 | 20 | const prove = useAsyncCallback(async () => { 21 | const parsedInputs = JSON.parse(inputs) 22 | const output = run(code, parsedInputs) 23 | setOutput(output) 24 | setReport(JSON.stringify({ 25 | proof: encodeBase64(output.proof()), 26 | proofLength: output.proof().length, 27 | cycleCount: output.cycle_count(), 28 | // this: hasThis ? output.this() : null, 29 | logs: output.logs(), 30 | hashes: output.hashes(), 31 | // selfDestructed: output.self_destructed(), 32 | readAuth: output.read_auth(), 33 | }, null, 2)) 34 | }) 35 | 36 | const verify = useAsyncCallback(() => { 37 | if (!output) { 38 | return toast({ 39 | status: 'error', 40 | title: 'No proof', 41 | description: 'There is no proof to verify', 42 | duration: 9000, 43 | }) 44 | } 45 | 46 | const proof = output.proof() 47 | const programInfo = output.program_info() 48 | const stackInputs = output.stack_inputs() 49 | const outputStack = output.output_stack() 50 | const overflowAddrs = output.overflow_addrs() 51 | 52 | const time = Date.now() 53 | verifyProof(proof, programInfo, stackInputs, outputStack, overflowAddrs) 54 | const diff = Date.now() - time 55 | 56 | toast({ 57 | status: 'success', 58 | title: 'Valid Proof', 59 | description: `Proof was verified in ${diff}ms`, 60 | duration: 9000, 61 | }) 62 | }) 63 | 64 | return ( 65 | 66 | 67 | Inputs 68 | 69 | { 70 | setInputs(inputs) 71 | setOutput(null) 72 | setReport('') 73 | }} /> 74 | 75 | 76 | 77 | 78 | Code 79 | 80 | { 81 | setCode(code) 82 | setOutput(null) 83 | setReport('') 84 | }} /> 85 | 86 | 87 | 88 | 89 | 90 | 91 | Output 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | ) 100 | } 101 | 102 | export default Prover -------------------------------------------------------------------------------- /site/components/Why.tsx: -------------------------------------------------------------------------------- 1 | import { Card, Text, Heading, SimpleGrid, Stack, useColorModeValue } from '@chakra-ui/react' 2 | 3 | const WhyBlock = ({ title, children }) => { 4 | return ( 5 | 6 | 7 | {title} 8 | {children} 9 | 10 | 11 | ) 12 | } 13 | 14 | const Why = () => { 15 | return ( 16 | 17 | Why? 18 | 19 | 20 | Run computations and generate a proof that the computation was run as designed. 21 | 22 | 23 | Hide the inputs/outputs to the program while ensuring , to enable end user privacy. 24 | 25 | 26 | Define state (similar to TypeScript class or Solidity contract) to allow provable state transitions. 27 | 28 | 29 | Inputs to functions (and contract state) are hashed, allowing commitments to inputs/outputs to be made. 30 | 31 | 32 | Compiles to WASM so computation and proof generation, as well as verification, can be done in the browser. 33 | 34 | 35 | The language you know and love already, ported to run in zero knowledge. 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | export default Why -------------------------------------------------------------------------------- /site/components/example.ts: -------------------------------------------------------------------------------- 1 | export const EXAMPLES = [{ 2 | name: 'Hello, World!', 3 | code: ` function hello() { 4 | log("Hello, world!"); 5 | }`, 6 | inputs: `{ 7 | "init_params": "", 8 | "params": [], 9 | "contract_name": "", 10 | "fn": "hello" 11 | }`, 12 | }, { 13 | name: 'Addition', 14 | code: `function main(a: u32, b: u32): u32 { 15 | return a + b; 16 | }`, 17 | inputs: `{ 18 | "init_params": "", 19 | "params": [10, 20], 20 | "contract_name": "", 21 | "fn": "main" 22 | }`, 23 | }, { 24 | name: 'Fibonacci', 25 | code: `function main(p: u32, a: u32, b: u32) { 26 | for (let i: u32 = 0; i < p; i++) { 27 | let c = a.wrappingAdd(b); 28 | a = b; 29 | b = c; 30 | } 31 | }`, 32 | inputs: `{ 33 | "init_params": "", 34 | "params": [8, 1, 1], 35 | "contract_name": "", 36 | "fn": "main" 37 | }`, 38 | }, { 39 | name: 'Hello, Contracts!', 40 | code: `contract HelloContracts { 41 | function hello() { 42 | log("Hello, contracts!"); 43 | } 44 | }`, 45 | inputs: `{ 46 | "init_params": "", 47 | "params": [], 48 | "contract_name": "HelloContracts", 49 | "fn": "hello" 50 | }`, 51 | }, { 52 | name: 'Reverse Array', 53 | code: `contract ReverseArray { 54 | elements: number[]; 55 | 56 | constructor (elements: number[]) { 57 | this.elements = elements; 58 | } 59 | 60 | function reverse(): number[] { 61 | let reversed: u32[] = []; 62 | let i: u32 = 0; 63 | let one: u32 = 1; 64 | let len: u32 = this.elements.length; 65 | 66 | while (i < len) { 67 | let idx: u32 = len - i - one; 68 | reversed.push(this.elements[idx]); 69 | i = i + one; 70 | } 71 | 72 | return reversed; 73 | } 74 | } 75 | `, 76 | inputs: `{ 77 | "init_params": { "elements": [1, 2, 3, 4, 5] }, 78 | "params": [], 79 | "contract_name": "ReverseArray", 80 | "fn": "reverse" 81 | }`, 82 | }, { 83 | name: 'Binary Search', 84 | code: `contract BinarySearch { 85 | arr: i32[]; 86 | found: boolean; // indicates whether element was found 87 | foundPos: u32; // gives the position of the found element 88 | 89 | constructor (arr: i32[]) { 90 | this.arr = arr; 91 | } 92 | 93 | function search(elem: i32): map { 94 | let low: u32 = 0; 95 | let high: u32 = this.arr.length; 96 | let one: u32 = 1; 97 | let two: u32 = 2; 98 | 99 | while (low <= high) { 100 | let mid: u32 = low + high; 101 | mid = mid / two; 102 | 103 | if (this.arr[mid] < elem) { 104 | low = mid + one; 105 | } else { 106 | if (this.arr[mid] > elem) { 107 | high = mid - one; 108 | } else { 109 | this.found = true; // found 110 | this.foundPos = mid; 111 | break; 112 | } 113 | } 114 | } 115 | } 116 | } 117 | `, 118 | inputs: `{ 119 | "init_params" : { "arr": [1, 2, 3, 3, 5, 6, 11], "found": false, "foundPos": 0 }, 120 | "params": [5], 121 | "contract_name": "BinarySearch", 122 | "fn": "search" 123 | }`, 124 | }, { 125 | name: 'City and Country', 126 | code: `contract City { 127 | id: string; 128 | name: string; 129 | country: Country; // reference to the Country contract 130 | 131 | constructor (id: string, name: string, country: Country) { 132 | this.id = id; 133 | this.name = name; 134 | this.country = country; 135 | } 136 | } 137 | 138 | contract Country { 139 | id: string; 140 | name: string; 141 | 142 | constructor (id: string, name: string) { 143 | this.id = id; 144 | this.name = name; 145 | } 146 | }`, 147 | inputs: `{ 148 | "init_params": { "id": "", "name": "", "country": { "id": "", "name": "" } }, 149 | "params": ["boston", "BOSTON", { "id": "usa", "name": "USA"}], 150 | "contract_name": "City", 151 | "fn": "constructor" 152 | }`, 153 | }] -------------------------------------------------------------------------------- /site/components/pkg/index.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | * @param {string} code 5 | * @param {string | undefined} contract_name 6 | * @param {string} fn_name 7 | * @returns {Program} 8 | */ 9 | export function compile(code: string, contract_name: string | undefined, fn_name: string): Program; 10 | /** 11 | * @param {Uint8Array | undefined} proof 12 | * @param {any} program_info 13 | * @param {any[]} stack_inputs 14 | * @param {any[]} output_stack 15 | * @param {any[]} overflow_addrs 16 | * @returns {boolean} 17 | */ 18 | export function verify(proof: Uint8Array | undefined, program_info: any, stack_inputs: any[], output_stack: any[], overflow_addrs: any[]): boolean; 19 | /** 20 | */ 21 | export function init(): void; 22 | /** 23 | * @param {string} input 24 | * @param {string} namespace 25 | * @returns {string} 26 | */ 27 | export function parse(input: string, namespace: string): string; 28 | /** 29 | * @param {string} ast_json 30 | * @param {string} data_json 31 | * @returns {string} 32 | */ 33 | export function validate_set(ast_json: string, data_json: string): string; 34 | /** 35 | * @param {string} contract_ast_json 36 | * @returns {string} 37 | */ 38 | export function generate_js_contract(contract_ast_json: string): string; 39 | /** 40 | */ 41 | export class Output { 42 | free(): void; 43 | /** 44 | * @returns {number} 45 | */ 46 | cycle_count(): number; 47 | /** 48 | * @returns {Uint8Array | undefined} 49 | */ 50 | proof(): Uint8Array | undefined; 51 | /** 52 | * @returns {any} 53 | */ 54 | program_info(): any; 55 | /** 56 | * @returns {any[]} 57 | */ 58 | stack_inputs(): any[]; 59 | /** 60 | * @returns {any[]} 61 | */ 62 | output_stack(): any[]; 63 | /** 64 | * @returns {any[]} 65 | */ 66 | overflow_addrs(): any[]; 67 | /** 68 | * @returns {any} 69 | */ 70 | this(): any; 71 | /** 72 | * @returns {any} 73 | */ 74 | result(): any; 75 | /** 76 | * @returns {any} 77 | */ 78 | result_hash(): any; 79 | /** 80 | * @returns {any} 81 | */ 82 | hashes(): any; 83 | /** 84 | * @returns {any} 85 | */ 86 | logs(): any; 87 | /** 88 | * @returns {boolean} 89 | */ 90 | self_destructed(): boolean; 91 | /** 92 | * @returns {boolean} 93 | */ 94 | read_auth(): boolean; 95 | } 96 | /** 97 | */ 98 | export class Program { 99 | free(): void; 100 | /** 101 | * @returns {string} 102 | */ 103 | miden_code(): string; 104 | /** 105 | * @param {string} this_json 106 | * @param {string} args_json 107 | * @param {boolean} generate_proof 108 | * @returns {Output} 109 | */ 110 | run(this_json: string, args_json: string, generate_proof: boolean): Output; 111 | } 112 | -------------------------------------------------------------------------------- /site/components/pkg/index.js: -------------------------------------------------------------------------------- 1 | export * from "./index_bg.js"; 2 | -------------------------------------------------------------------------------- /site/components/pkg/index_bg.wasm.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | export const memory: WebAssembly.Memory; 4 | export function __wbg_program_free(a: number): void; 5 | export function compile(a: number, b: number, c: number, d: number, e: number, f: number, g: number): void; 6 | export function program_miden_code(a: number, b: number): void; 7 | export function program_run(a: number, b: number, c: number, d: number, e: number, f: number, g: number): void; 8 | export function __wbg_output_free(a: number): void; 9 | export function output_cycle_count(a: number): number; 10 | export function output_proof(a: number, b: number): void; 11 | export function output_program_info(a: number): number; 12 | export function output_stack_inputs(a: number, b: number): void; 13 | export function output_output_stack(a: number, b: number): void; 14 | export function output_overflow_addrs(a: number, b: number): void; 15 | export function output_this(a: number, b: number): void; 16 | export function output_result(a: number, b: number): void; 17 | export function output_result_hash(a: number, b: number): void; 18 | export function output_hashes(a: number, b: number): void; 19 | export function output_logs(a: number, b: number): void; 20 | export function output_self_destructed(a: number, b: number): void; 21 | export function output_read_auth(a: number): number; 22 | export function verify(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number): void; 23 | export function init(): void; 24 | export function parse(a: number, b: number, c: number, d: number, e: number): void; 25 | export function validate_set(a: number, b: number, c: number, d: number, e: number): void; 26 | export function generate_js_contract(a: number, b: number, c: number): void; 27 | export function __wbindgen_malloc(a: number, b: number): number; 28 | export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number; 29 | export function __wbindgen_add_to_stack_pointer(a: number): number; 30 | export function __wbindgen_free(a: number, b: number, c: number): void; 31 | -------------------------------------------------------------------------------- /site/components/polylang.ts: -------------------------------------------------------------------------------- 1 | import * as pkg from './pkg' 2 | export * from './pkg' 3 | 4 | export interface Inputs { 5 | init_params: any, 6 | params: any[], 7 | contract_name: string | null, 8 | fn: string, 9 | } 10 | 11 | pkg.init() 12 | 13 | export function run(code: string, inputs: Inputs) { 14 | console.log(code, inputs) 15 | const program = pkg.compile( 16 | code, 17 | inputs.contract_name === '' ? null : inputs.contract_name, 18 | inputs.fn 19 | ) 20 | 21 | let output = program.run( 22 | JSON.stringify(inputs.init_params), 23 | JSON.stringify(inputs.params), 24 | // true == generate a proof 25 | true 26 | ) 27 | 28 | return output 29 | } 30 | 31 | // Server prover/verifier 32 | 33 | export interface ServerOutput { 34 | old: { 35 | this: any, 36 | hashes: string[], 37 | }, 38 | new: { 39 | selfDestructed: boolean, 40 | this: any, 41 | hashes: string[], 42 | }, 43 | stack: { 44 | input: string[], 45 | output: string[], 46 | overflowAddrs: string[], 47 | }, 48 | result?: { 49 | value: any, 50 | hash: string, 51 | }, 52 | programInfo: string, 53 | proof: string, 54 | debug: { 55 | logs: string[], 56 | }, 57 | cycleCount: number, 58 | proofLength: number, 59 | logs: any[], 60 | readAuth: boolean, 61 | } 62 | 63 | export function compile(code: string, inputs: Inputs) { 64 | let program = pkg.compile( 65 | code, 66 | inputs.contract_name === '' ? null : inputs.contract_name, 67 | inputs.fn 68 | ) 69 | 70 | const midenCode = program.miden_code() 71 | 72 | const abiStringMatch = midenCode.match(/# ABI: (.+?)\n/) 73 | 74 | if (!abiStringMatch) { 75 | console.log('Could not extract abi from miden code') 76 | return null 77 | } 78 | 79 | const abiString = abiStringMatch[1] 80 | const abi = JSON.parse(abiString) 81 | return { midenCode: midenCode, abi: abi } 82 | } -------------------------------------------------------------------------------- /site/components/useAsyncCallback.ts: -------------------------------------------------------------------------------- 1 | import { useAsyncCallback as useAsyncCallbackBase, UseAsyncCallbackOptions } from 'react-async-hook' 2 | import { useToast } from '@chakra-ui/react' 3 | 4 | export type ExtendedOptions = UseAsyncCallbackOptions & { 5 | logError?: boolean 6 | errorTitle?: string 7 | successTitle?: string 8 | } 9 | 10 | export function useAsyncCallback(asyncFunction: (...args: Args) => Promise | R, options?: ExtendedOptions) { 11 | const { onError, errorTitle, logError, successTitle, ...baseOptions } = options || {} 12 | const toast = useToast() 13 | return useAsyncCallbackBase(async (...params: Args) => { 14 | const res = await asyncFunction(...params) 15 | if (successTitle) { 16 | toast({ 17 | status: 'success', 18 | title: successTitle, 19 | }) 20 | } 21 | return res 22 | }, { 23 | ...baseOptions, 24 | onError: (e, options) => { 25 | if (onError) onError(e, options) 26 | 27 | if (e?.message === 'unreachable') { 28 | e.message = 'Proof aborted, program uses too much RAM for browser environment' 29 | } 30 | 31 | 32 | toast({ 33 | status: 'error', 34 | title: errorTitle, 35 | description: e?.message, 36 | isClosable: true, 37 | duration: 9000, 38 | }) 39 | }, 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /site/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /site/img/made-by/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polybase/polylang/5257cb206079fbbd2e7244359895eac9bd300e1b/site/img/made-by/dark.png -------------------------------------------------------------------------------- /site/img/made-by/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polybase/polylang/5257cb206079fbbd2e7244359895eac9bd300e1b/site/img/made-by/light.png -------------------------------------------------------------------------------- /site/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /site/next.config.js: -------------------------------------------------------------------------------- 1 | const withNextra = require('nextra')({ 2 | theme: 'nextra-theme-docs', 3 | themeConfig: './theme.config.tsx', 4 | }) 5 | 6 | module.exports = withNextra() 7 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polylang-site", 3 | "version": "0.0.1", 4 | "description": "Polylang site & docs", 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "eslint \"./components/**/*.{ts,tsx}\"", 10 | "fix": "eslint \"./components/**/*.{ts,tsx}\" --fix" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/polybase/polylang" 15 | }, 16 | "author": "Polybase Labs ", 17 | "bugs": { 18 | "url": "https://github.com/polybase/polylang-site/issues" 19 | }, 20 | "homepage": "https://github.com/polybase/polylang-site", 21 | "dependencies": { 22 | "@chakra-ui/next-js": "^2.1.5", 23 | "@chakra-ui/react": "^2.8.0", 24 | "@codemirror/lang-json": "^6.0.1", 25 | "@codemirror/view": "^6.21.0", 26 | "@emotion/react": "^11.11.1", 27 | "@emotion/styled": "^11.11.0", 28 | "@polybase/codemirror-lang-javascript": "^6.1.2", 29 | "@uiw/codemirror-theme-github": "^4.21.18", 30 | "@uiw/codemirror-theme-vscode": "^4.21.18", 31 | "@uiw/react-codemirror": "^4.21.18", 32 | "framer-motion": "^10.16.4", 33 | "next": "^13.0.6", 34 | "nextra": "latest", 35 | "nextra-theme-docs": "latest", 36 | "posthog-js": "^1.81.2", 37 | "react": "^18.2.0", 38 | "react-async-hook": "^4.0.0", 39 | "react-dom": "^18.2.0", 40 | "react-icons": "^4.11.0", 41 | "tweetnacl-util": "^0.15.1" 42 | }, 43 | "eslintConfig": { 44 | "root": true, 45 | "extends": [ 46 | "next" 47 | ], 48 | "plugins": ["@typescript-eslint"], 49 | "parser": "@typescript-eslint/parser", 50 | "ignorePatterns": ["components/pkg/*"], 51 | "rules": { 52 | "react/prop-types": [ 53 | 2, 54 | { 55 | "skipUndeclared": true 56 | } 57 | ], 58 | "react/jsx-handler-names": "off", 59 | "quotes": "off", 60 | "jsx-quotes": [ 61 | 2, 62 | "prefer-single" 63 | ], 64 | "comma-dangle": [ 65 | 2, 66 | "always-multiline" 67 | ], 68 | "object-curly-spacing": [ 69 | 2, 70 | "always" 71 | ], 72 | "react/display-name": 0, 73 | "semi": [ 74 | "error", 75 | "never" 76 | ], 77 | "no-underscore-dangle": 0, 78 | "space-before-function-paren": 0, 79 | "arrow-body-style": 0, 80 | "no-use-before-define": 0, 81 | "arrow-parens": 0, 82 | "no-trailing-spaces": "error", 83 | "@typescript-eslint/quotes": [ 84 | "error", 85 | "single" 86 | ], 87 | "@typescript-eslint/indent": [ 88 | "error", 89 | 2 90 | ], 91 | "keyword-spacing": "error", 92 | "key-spacing": [ 93 | "error", 94 | { 95 | "mode": "strict" 96 | } 97 | ] 98 | } 99 | }, 100 | "devDependencies": { 101 | "@types/node": "18.11.10", 102 | "@typescript-eslint/eslint-plugin": "^6.7.3", 103 | "@typescript-eslint/parser": "^6.7.3", 104 | "eslint": "^8.50.0", 105 | "eslint-config-next": "^13.5.3", 106 | "typescript": "^4.9.3" 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /site/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useEffect } from 'react' 4 | import { useRouter } from 'next/router' 5 | import { ChakraProvider } from '@chakra-ui/react' 6 | import { theme } from '../theme' 7 | import posthog from 'posthog-js' 8 | import { PostHogProvider } from 'posthog-js/react' 9 | 10 | // Check that PostHog is client-side (used to handle Next.js SSR) 11 | if (typeof window !== 'undefined') { 12 | posthog.init('phc_YlHyyk6r6X70YO0htIowt5TlKTqTwU4VqnITPniYRIA', { 13 | api_host: 'https://a.polybase.xyz', 14 | // Enable debug mode in development 15 | loaded: (posthog) => { 16 | if (process.env.NODE_ENV === 'development') posthog.debug() 17 | }, 18 | capture_pageview: false, // Disable automatic pageview capture, as we capture manually 19 | }) 20 | } 21 | 22 | function MyApp({ Component, pageProps }) { 23 | const router = useRouter() 24 | 25 | useEffect(() => { 26 | // Track page views 27 | const handleRouteChange = () => posthog?.capture('$pageview') 28 | router.events.on('routeChangeComplete', handleRouteChange) 29 | 30 | return () => { 31 | router.events.off('routeChangeComplete', handleRouteChange) 32 | } 33 | }, []) 34 | 35 | return ( 36 | 37 | 38 | 39 | 40 | 41 | ) 42 | } 43 | 44 | export default MyApp -------------------------------------------------------------------------------- /site/pages/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": { 3 | "title": "Polylang", 4 | "type": "page", 5 | "display": "hidden", 6 | "theme": { 7 | "footer": false, 8 | "navbar": false, 9 | "layout": "full" 10 | } 11 | }, 12 | "docs": { 13 | "type": "page", 14 | "title": "Documentation" 15 | }, 16 | "playground": { 17 | "title": "Playground", 18 | "type": "page", 19 | "theme": { 20 | "footer": false, 21 | "navbar": false, 22 | "layout": "full" 23 | } 24 | }, 25 | "about": { 26 | "title": "About", 27 | "type": "page" 28 | }, 29 | "contact": { 30 | "title": "Contact ↗", 31 | "type": "page", 32 | "href": "https://twitter.com/polybase_xyz", 33 | "newWindow": true 34 | } 35 | } -------------------------------------------------------------------------------- /site/pages/about.mdx: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | We are [Polybase Labs](https://polybaselabs.com/). 4 | -------------------------------------------------------------------------------- /site/pages/docs/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": "Introducing Polylang", 3 | "getting-started": "Getting Started", 4 | "zk-proofs": "Zero-Knowledge Proofs", 5 | "language-features": "Language Features" 6 | } -------------------------------------------------------------------------------- /site/pages/docs/getting-started/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "building-from-source": "Building Polylang from Source", 3 | "polylang-cli": "Polylang CLI", 4 | "polylang-lib": "Polylang Library" 5 | } -------------------------------------------------------------------------------- /site/pages/docs/getting-started/building-from-source.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Building from Source 3 | --- 4 | 5 | # Building `Polylang` from Source 6 | 7 | `Polylang` can be built as a Rust (library) crate, and used via a client written in Rust. 8 | 9 | First, clone the `Polylang` repo: 10 | 11 | ```bash 12 | $ git clone https://github.com/polybase/polylang 13 | ``` 14 | 15 | Then build the project (use `--release` to generate a release build): 16 | 17 | ```bash 18 | $ cd polylang/ 19 | $ cargo build 20 | ``` 21 | 22 | `Polylang` generates both static and dynamic libraries, as can be seen from its `Cargo.toml`: 23 | 24 | ```toml 25 | [lib] 26 | crate-type = ["staticlib", "cdylib", "rlib"] 27 | ``` 28 | 29 | ## Sample Usage 30 | 31 | We can use `Polylang` via its CLI interface, or using it as a library dependency. Each is demonstrated in the following sections. 32 | 33 | -------------------------------------------------------------------------------- /site/pages/docs/getting-started/polylang-cli.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Polylang CLI 3 | --- 4 | 5 | import { Callout } from 'nextra/components' 6 | 7 | # `Polylang` CLI 8 | 9 | This assumes that you have cloned the repository locally, and built it as described in the [Building Polylang](/docs/getting-started/building-from-source) section. 10 | 11 | Suppose we have a contract representing an account: 12 | 13 | ```typescript 14 | contract Account { 15 | id: string; 16 | name: string; 17 | 18 | setName(newName: string) { 19 | this.name = newName; 20 | } 21 | } 22 | ``` 23 | 24 | Suppose we have a sample record like so: 25 | 26 | ```json 27 | { 28 | "id": "id1", 29 | "name": "John" 30 | } 31 | ``` 32 | 33 | We wish to change the value of the `name` field from "John" to "Tom" by invoking the `setName` function defined in the contract. 34 | 35 | We can compile and run this contract using the `Polylang` CLI as explained next: 36 | 37 | ```bash 38 | $ cargo run --bin compile -- contract:Account function:setName <<< 'contract Account { id: string; name: string; function setName(newName: string) { this.name = newName; } }' \ 39 | | cargo run -p miden-run -- \ 40 | --this-json '{ "id": "id1", "name": "John" }' \ 41 | --advice-tape-json '["Tom"]' 42 | ``` 43 | 44 | A brief explanation of the command: 45 | * We compile the contract passing in the contract name (using the `contract:` marker) and the function of interest (using the `function:` marker) as well as the body of the contract itself. 46 | * The compilation step produces bytecode for the Miden VM as well as a `Polylang` specific ABI JSON text representing the processed contract. 47 | * We then pipe the output from the compilation step to the `miden-run` executable by passing in: 48 | 1. the record input using the `--this-json` flag. In this case, it's a contract record containing two fields: `id` and `name`. 49 | 2. the new value for the `name` field using the `--advice-json` flag. 50 | 51 | This produces: 52 | 53 | ```bash 54 | 55 | this_json: {"id":"id1","name":"Tom"} 56 | ``` 57 | 58 | As we can see, the record has been updated, reflecting the name change: 59 | 60 | ```json 61 | { 62 | "id": "id1", 63 | "name": "Tom" 64 | } 65 | ``` -------------------------------------------------------------------------------- /site/pages/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introducing Polylang 3 | --- 4 | 5 | # Introducing Polylang 6 | 7 | ## What is Polylang? 8 | 9 | `Polylang` is a statically (and strongly typed) smart contract programming language, with syntax and features influenced by languages such as `JavaScript` and `TypeScript`, used by [Polybase](https://polybase.xyz/) for writing smart contracts[^1] 10 | whereby proofs can be generated that state transitions follow the contract's rules, and which compiles down to [Zero-Knowledge](https://en.wikipedia.org/wiki/Zero-knowledge_proof) programs that are verified using zk-STARKs. 11 | 12 | Zero-Knowledge provides two main benefits: 13 | * Provable computation - proves that the code ran as designed, and therefore the output can be trusted. 14 | * Privacy - the user can prove that they possess certain information without revealing the information itself. 15 | 16 | Refer to the page on [Zero-Knowledge](/docs/zk-proofs/what-is-zk) for more details. 17 | 18 | ## Overview 19 | 20 | Using `Polylang`, you can define contract fields, and add methods (functions associated with the contract) that operate on the data (fields). 21 | 22 | An example of a typical smart contract in `Polylang` is: 23 | 24 | ```typescript 25 | contract Person { 26 | id: string; 27 | name: string; 28 | age: number; 29 | 30 | // create a new record for this smart contract 31 | constructor (id: string, name: string, age: number) { 32 | this.id = id; 33 | this.name = name; 34 | this.age = age; 35 | } 36 | 37 | // update the name for this record 38 | setName(newName: string) { 39 | this.name = newName; 40 | } 41 | 42 | // update the age for this record 43 | setAge(newAge: number) { 44 | this.age = age; 45 | } 46 | } 47 | ``` 48 | 49 | The code above defines a smart contract called `Person` which defines a set of fields associated with `Person`. In this case, we have three fields: 50 | * the `id` field which uniquely identifies a "record" of this contract 51 | * a `name` field for storing the name of the person 52 | * an `age` field for storing the age of the person 53 | 54 | Like other smart contract languages, `Polylang` allows the updating of a contract's state by calling functions/methods on the contract. 55 | In the contract above, we have defined a couple of functions that can update the `name` and `age` fields of the contract. 56 | 57 | ## Using Polylang 58 | 59 | `Polylang` is open-source and implemented in Rust. It can be used as a CLI or as a library (crate). Head to the next section to get started with `Polylang`. 60 | 61 | ## Examples 62 | 63 | For full-fledged and runnable examples, please refer to the [examples](https://github.com/polybase/polylang/showcase) in the `Polylang` repository. 64 | 65 | [^1]: See the section on [contracts](https://polybase.xyz/docs/collections) in the `Polybase` documentation for more details. 66 | -------------------------------------------------------------------------------- /site/pages/docs/language-features/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "lexical-structure": "Lexical Structure", 3 | "context": "Context", 4 | "fields": "Fields", 5 | "fieldtypes": "Field Types", 6 | "errors": "Errors and Error Handling", 7 | "bindings": "Local Variable Bindings", 8 | "constructors": "Constructors", 9 | "functions": "Functions", 10 | "control-flow": "Control Flow", 11 | "permissions": "Declarative Permissions", 12 | "general-structure": "Structure of Programs" 13 | } -------------------------------------------------------------------------------- /site/pages/docs/language-features/bindings.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Local Variable Bindings 3 | --- 4 | 5 | # Local Variable Bindings 6 | 7 | import { Callout } from 'nextra/components' 8 | 9 | `Polylang` allows bindings for variables inside functions using the `let` construct. 10 | The syntax is similar to that of `TypeScript` (or `Rust`): 11 | 12 | ```typescript 13 | `let` `=` 14 | ``` 15 | 16 | Each binding must have an initial value assigned to it. Unitialized bindings are not allowed in `Polylang`. Types are automatically inferred using Type Inference. 17 | 18 | 19 | You cannot currently provide explicit types. This will be possible soon. 20 | 21 | 22 | Example: 23 | 24 | ```typescript 25 | contract Person { 26 | id: string; 27 | name: string; 28 | age: number; 29 | 30 | constructor (id: string, name: string, age: number) { 31 | this.id = id; 32 | this.name = name; 33 | this.age = age; 34 | } 35 | 36 | // increment the age by the given value 37 | incrementAgeBy(inc: number) { 38 | // bind the variable `oldAge` to the current value of `age` for this record 39 | let oldAge = this.age; 40 | // bind `newAge` to the old age increment by `inc` 41 | let newAge = oldAge + inc; 42 | // update the `age` field 43 | this.age = newAge; 44 | } 45 | } 46 | ``` 47 | 48 | Here we have a function `incrementAgeBy` which takes an increment value `inc`, and updates the `age` field of the record by that increment value. To be able to reference the old value of `age`, we create a couple of 49 | local binding called `oldAge` and `newAge`. -------------------------------------------------------------------------------- /site/pages/docs/language-features/constructors.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Constructors 3 | --- 4 | 5 | # Constructors 6 | 7 | import { Callout } from 'nextra/components' 8 | 9 | A constructor is a special `Polylang` function which is used to create a new record of a contract. For the specific signature of the constructor, 10 | refer to the [simplified grammar](general-structure). 11 | 12 | For example, suppose we have the following contract: 13 | 14 | ```typescript 15 | contract Person { 16 | id: string; 17 | name: string; 18 | age: number; 19 | 20 | constructor (id: string, name; string, age: number) { 21 | this.id = id; 22 | this.name = name; 23 | this.age = age; 24 | } 25 | } 26 | ``` 27 | 28 | The constructor in this case has the following fields: `id`, `name`, and `age`, which map to the fields of the contract. When a new record of the `Person` contract is created, 29 | the constructor is invoked, passed these values, and the fields for this record are updated with these values. 30 | 31 | 32 | Note that the constructor arguments are not required to match the field names. 33 | 34 | 35 | The constructor can take fewer arguments than the number of fields in the contract: 36 | 37 | ```typescript 38 | contract Person { 39 | id: string; 40 | name: string; 41 | age: number; 42 | 43 | constructor (id: string, name; string) { 44 | this.id = id; 45 | this.name = name; 46 | this.age = 0; // initialize explicitly 47 | } 48 | } 49 | ``` -------------------------------------------------------------------------------- /site/pages/docs/language-features/context.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: context 3 | --- 4 | 5 | # Context 6 | 7 | The context object (accessed via the `ctx` object) is a special value that holds the public key needed for authorization checks by the program. 8 | 9 | Suppose we have this contract: 10 | 11 | ```typescript 12 | contract User { 13 | id: string; 14 | name: string; 15 | publicKey: PublicKey; 16 | 17 | constructor (id: string, name: string) { 18 | this.id = id; 19 | this.name = name; 20 | // extract the public key from the context 21 | this.publicKey = ctx.publicKey; 22 | } 23 | 24 | function setName(newName: string) { 25 | // check that the public keys match before 26 | // allowing function call. 27 | if (ctx.publicKey != this.publicKey) { 28 | error('Unauthorized access!) 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | To pass in the context, pass in the signature like so: 35 | 36 | ```bash 37 | --ctx '{ "publicKey": {"kty": "EC", "crv": "secp256k1", "alg": "ES256K", "use": "sig", "x": "nnzHFO4bZ239bIuAo8t0wQwXH3fPwbKQnpWPzOptv0Q=", "y": "Z1-oY62A6q5kCRGfBuk6E3IrSUjPCK2F6_EwVhW22lY="} }' 38 | ``` 39 | 40 | For example: 41 | 42 | ```bash 43 | $ cargo run --bin compile -- contract:Account function:setName <<< \ 44 | 'contract User { 45 | id: string; 46 | name: string; 47 | publicKey: PublicKey; 48 | 49 | constructor (id: string, name: string) { 50 | this.id = id; 51 | this.name = name; 52 | // extract the public key from the context 53 | this.publicKey = ctx.publicKey; 54 | } 55 | 56 | function setName(newName: string) { 57 | // check that the public keys match before 58 | // allowing function call. 59 | if (ctx.publicKey != this.publicKey) { 60 | error('Unauthorized access!) 61 | } 62 | } 63 | }' \ 64 | | cargo run -p miden-run -- \ 65 | --ctx '{ "publicKey": {"kty": "EC", "crv": "secp256k1", "alg": "ES256K", "use": "sig", "x": "nnzHFO4bZ239bIuAo8t0wQwXH3fPwbKQnpWPzOptv0Q=", "y": "Z1-oY62A6q5kCRGfBuk6E3IrSUjPCK2F6_EwVhW22lY="} }' \ 66 | --this-json '{ "id": "id1", "name": "John"}' \ 67 | --advice-tape-json '["Tom"]' 68 | ``` -------------------------------------------------------------------------------- /site/pages/docs/language-features/control-flow.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Control Flow 3 | --- 4 | 5 | # Control Flow 6 | 7 | `Polylang` provides the typical control flow constructs found in most programming languages. 8 | 9 | ## Conditional branching 10 | 11 | Conditional branching is provided by the `if-else` construct. The `else` part is optional. 12 | 13 | A simple example: 14 | 15 | ```typescript 16 | contract Person { 17 | id: string; 18 | name: string; 19 | age: number; 20 | canVote: boolean; 21 | 22 | constructor (id: string, name: string, age: number) { 23 | this.id = id; 24 | this.name = name; 25 | this.age = age; 26 | } 27 | 28 | checkVotingCapability() { 29 | if (this.age >= 18) { 30 | this.canVote = true; 31 | } else { 32 | error('you have a long wait ahead of you!'); 33 | } 34 | } 35 | } 36 | ``` 37 | 38 | Note that we cannot chain `if-else`. For instance, the following code is **not** valid: 39 | 40 | ```typescript 41 | contract Person { 42 | id: string; 43 | name: string; 44 | age: number; 45 | canVote: boolean; 46 | 47 | constructor (id: string, name: string, age: number) { 48 | this.id = id; 49 | this.name = name; 50 | this.age = age; 51 | this.canVote = false; 52 | } 53 | 54 | checkVotingCapability() { 55 | if (this.age >= 18) { 56 | this.canVote = true; 57 | } else if (this.age >= 12) { // this is not allowed 58 | throw error('wait a few more years!'); 59 | } else { 60 | throw error('you have a long wait ahead of you!'); 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | Instead, what you can do is to nest them like so: 67 | 68 | 69 | ```typescript 70 | contract Person { 71 | id: string; 72 | name: string; 73 | age: number; 74 | canVote: boolean; 75 | 76 | constructor (id: string, name: string, age: number) { 77 | this.id = id; 78 | this.name = name; 79 | this.age = age; 80 | this.canVote = false; 81 | } 82 | 83 | checkVotingCapability() { 84 | if (this.age >= 18) { 85 | this.canVote = true; 86 | } else { 87 | // this is allowed 88 | if (this.age >= 12) { 89 | throw error('wait a few more years!'); 90 | } else { 91 | throw error('you have a long wait ahead of you!'); 92 | } 93 | } 94 | } 95 | } 96 | ``` 97 | 98 | 99 | ## Loops 100 | 101 | `Polylang` has two looping constructs - `for` and `while`. These are similar to their analogs in C-like language. 102 | 103 | ### for 104 | 105 | We also have `TypeScript` like `for` loops. See the `incrementAgeMagically` function below: 106 | 107 | ```typescript 108 | contract Person { 109 | id: string; 110 | name: string; 111 | age: number; 112 | 113 | constructor (id: string, name: string, age: number) { 114 | this.id = id; 115 | this.name = name; 116 | this.age = age; 117 | } 118 | 119 | // increment the age the given number of times 120 | incrementAgeMagically(times: number) { 121 | for (let i = 0; i < times; i += 1) { 122 | this.age += 1; 123 | } 124 | } 125 | } 126 | ``` 127 | 128 | ## while 129 | 130 | The `while` loop again functions like the `while` loop in C-like languages: 131 | 132 | ```typescript 133 | contract Person { 134 | id: string; 135 | name: string; 136 | age: number; 137 | canVote: boolean; 138 | 139 | constructor (id: string, name: string, age: number) { 140 | this.id = id; 141 | this.name = name; 142 | this.age = age; 143 | this.canVote = false; 144 | } 145 | 146 | checkVotingCapability() { 147 | if (this.age >= 18) { 148 | this.canVote = true; 149 | } else { 150 | // this is allowed 151 | if (this.age >= 12) { 152 | throw error('wait a few more years!'); 153 | } else { 154 | throw error('you have a long wait ahead of you!'); 155 | } 156 | } 157 | } 158 | 159 | // increment the age by the given value 160 | incrementAgeMagically(inc: number) { 161 | let oldAge = this.age; 162 | while (this.age < oldAge + inc) { 163 | this.age += 1; 164 | } 165 | } 166 | } 167 | ``` 168 | Note the local binding produced:`let oldAge = this.age;` inside the `incrementAgeMagically` function. -------------------------------------------------------------------------------- /site/pages/docs/language-features/errors.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Errors and Error Handling 3 | --- 4 | 5 | # Errors and Error Handling 6 | 7 | `Polylang` provides error-handling support via the `error` **built-in** function. 8 | 9 | The syntax for `error` is: 10 | 11 | ```typescript 12 | error('error message goes here'); 13 | ``` 14 | 15 | We can also prepend the `error` built-in with a `throw`. So the following is identical: 16 | 17 | ```typescript 18 | throw error('error message goes here'); 19 | ``` 20 | 21 | The semantics is to abort the function call immediately with the given error message. 22 | 23 | Example: 24 | 25 | ```typescript 26 | contract Person { 27 | id: string; 28 | name: string; 29 | age: number; 30 | hasVoted: boolean; 31 | 32 | constructor (id: string, name: string, age: number) { 33 | this.id = id; 34 | this.name = name; 35 | this.age = age; 36 | this.canVote = false; 37 | } 38 | 39 | setName(newName: string) { 40 | this.name = newName; 41 | } 42 | 43 | setAge(newAge: number) { 44 | this.age = newAge; 45 | } 46 | 47 | vote() { 48 | if (this.age < 18) { 49 | // fail this call immediately 50 | error('Cannot vote till 18'); 51 | } 52 | 53 | this.hasVoted = true; 54 | } 55 | } 56 | ``` -------------------------------------------------------------------------------- /site/pages/docs/language-features/fields.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fields 3 | --- 4 | 5 | # Fields 6 | 7 | Data is stored in `Polybase` in the form of contracts. Each contract in turn defines the shape and structure of the data itself using fields. 8 | 9 | Field declarations must appear at the beginning of the contract declaration. In addition, each contract must have a unique field declaration of the form: 10 | 11 | ```typescript 12 | id : string; 13 | ``` 14 | 15 | This field is used as the unique identifier for a record (an instance) of the contract. Currently, the `id` field is only allowed to be of type `string`, but this 16 | restriction may be lifted in the future. 17 | 18 | ## Required fields 19 | 20 | By default, all fields in the contract are required. Required fields are usual field declarations. For instance: 21 | 22 | ```typescript 23 | contract Person { 24 | name: string; 25 | age: number; 26 | salary: number; 27 | qualified: boolean; 28 | 29 | ... 30 | } 31 | ``` 32 | 33 | ## Optional fields 34 | 35 | In addition, `Polylang` also allows fields to be declared as optional. This is done by adding a `?` to the field name: 36 | 37 | ```typescript 38 | contract Employee { 39 | employed?: boolean; 40 | balanceDetails?: number; 41 | 42 | ... 43 | } 44 | ``` 45 | 46 | ## Nested Fields 47 | 48 | Field declarations can also be nested. This is useful when the field itself is of composite nature (meaning that the sub-fields within this field are all related in some sense). For example: 49 | 50 | ```typescript 51 | contract Person { 52 | id: string; 53 | name: string; 54 | address: { 55 | street: string; 56 | city: string; 57 | country: Country; 58 | } 59 | 60 | ... 61 | } 62 | 63 | contract Country { 64 | id: string; 65 | name: string; 66 | countryCode: number; 67 | 68 | ... 69 | } 70 | ``` 71 | 72 | In the example above, the `address` field is a composite field consisting of sub-fields, one of which is a contract type itself (`Country`). -------------------------------------------------------------------------------- /site/pages/docs/language-features/fieldtypes.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Field Types 3 | --- 4 | 5 | # Field Types 6 | 7 | import { Callout } from 'nextra/components' 8 | 9 | `Polylang` provides support for the following types. 10 | 11 | ## Primitive types 12 | 13 | - `string`: a string of UTF-8 characters. 14 | - `number`: a numerical floating-point value (this type is used for both integral and floating-point values). 15 | - sized numeric types: 16 | * i32 - 32-bit signed integer 17 | * u32 - 32-bit unsigned integer 18 | * i64 - 64-bit signed integer 19 | * u64 - 64-bit unsigned integer 20 | * f32 - IEEE-754 [single-precision floating-point format](https://en.wikipedia.org/wiki/Single-precision_floating-point_format) 21 | * f32 - IEEE-754 [double-precision floating-point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) 22 | - `boolean`: a boolean value (`true` and `false`). 23 | - `bytes`: arbitrary bytes. 24 | 25 | Example: 26 | 27 | ```typescript 28 | contract Person { 29 | id: string; 30 | name: string; 31 | age: number; 32 | salary: u32; 33 | qualified: boolean; 34 | profileImage: bytes; 35 | } 36 | ``` 37 | 38 | ## PublicKey 39 | 40 | This is a special type used to represent a public key. Currently, only `secp256k1` (Ethereum) public keys are supported. However, support for [Falcon](https://eprint.iacr.org/2022/1041.pdf) 41 | public keys is in progress. 42 | 43 | This key is used in contracts to enforce a means of [Access Control](https://en.wikipedia.org/wiki/Access_control) in `Polybase`. 44 | 45 | Example: 46 | 47 | ```typescript 48 | contract User { 49 | id: string; 50 | name: string; 51 | publicKey: PublicKey; 52 | 53 | constructor (id: string, name: string) { 54 | this.id = id; 55 | this.name = name; 56 | this.publicKey = ctx.publicKey; 57 | } 58 | 59 | function setName(newName: string) { 60 | if (ctx.publicKey != this.publicKey) { 61 | error('Unauthorized access!) 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | For the `ctx.publicKey` part, please refer to the page on [Context](context). 68 | 69 | Later, in the `setName` function (also note the use of the optional `function` keyword), we can check the public key of the user invoking the function against the public key which was used 70 | at record creation time, and allow the operation only if they match. 71 | 72 | This check provides fine-grained checks over data updates. See the section on [permissions](permissions) for more details on permissions. 73 | 74 | ## Contract Types 75 | 76 | A contract type is simply a reference to a `Polybase` contract. Consider the following example: 77 | 78 | ```typescript 79 | contract Employee { 80 | id: string; 81 | name: string; 82 | empId: number; 83 | company: Company; 84 | 85 | constructor (id: string, name: string, empId: number, company: Company) { 86 | this.id = id; 87 | this.name = name; 88 | this.empid = empId; 89 | this.company = company; 90 | } 91 | } 92 | ``` 93 | 94 | The `company` field if of type `Company`, which is a contract declared as follows: 95 | 96 | ```typescript 97 | contract Company { 98 | id: string; 99 | name: string; 100 | code: string; 101 | 102 | constructor (id: string, name: string, code: string) { 103 | this.id = id; 104 | this.name = name; 105 | this.code = code; 106 | } 107 | } 108 | ``` 109 | 110 | ## Array Types 111 | 112 | An array in `Polylang` represents an ordered sequence of values of the same type (homogenous arrays). However, there are some restrictions on which types are allowed 113 | in array declarations. Only the following `Polylang` types are permitted: 114 | 115 | * [Primitive Types](#primitive-types) 116 | * [PublicKey](#publickey), and 117 | * [Contracts](#contract-types) 118 | 119 | Example: 120 | 121 | ```typescript 122 | contract Bank { 123 | id: string; 124 | accId: string; 125 | accounts: Account[]; 126 | 127 | ... 128 | } 129 | 130 | contract Account { 131 | id: string; 132 | name: string; 133 | balance: number; 134 | 135 | ... 136 | } 137 | ``` 138 | 139 | 140 | ## Map Types 141 | 142 | A map in `Polylang` represents, as expected, a mapping from the key type to the value type. However, the key can only be either a `string` or a `number` while the value can be any 143 | valid `Polylang` type. 144 | 145 | Example: 146 | 147 | ```typescript 148 | contract Bank { 149 | id: string; 150 | accId: string; 151 | accounts: map; 152 | 153 | ... 154 | } 155 | 156 | contract Account { 157 | id: string; 158 | name: string; 159 | balance: number; 160 | 161 | ... 162 | } 163 | 164 | ``` 165 | 166 | ## Object Types 167 | 168 | As in the case of [nested fields](fields/#nested-fields), an object type represents a composite contract of key-value pairs. 169 | 170 | Example: 171 | 172 | ```typescript 173 | contract Product { 174 | id: string; 175 | name: string; 176 | price: number; 177 | tags: string[]; 178 | details: { 179 | description: string; 180 | manufacturer: string; 181 | } 182 | 183 | constructor (id: string, name: string, price: number, description: string, manufacturer: string) { 184 | this.id = id; 185 | this.name = name; 186 | this.price = price; 187 | this.details = { 188 | description: description, 189 | manufacturer: manufacturer 190 | } 191 | } 192 | } 193 | ``` 194 | 195 | In the example above, the `details` field is of object type, and in the constructor, we populate this field by populating its nested fields with the values from the constructor arguments. -------------------------------------------------------------------------------- /site/pages/docs/language-features/functions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Functions 3 | --- 4 | 5 | # Functions 6 | 7 | import { Callout } from 'nextra/components' 8 | 9 | In addition to defining fields, types, and constructors for contracts, `Polylang` also allows developers to define custom functions within a contract. 10 | 11 | These functions can be used for various purposes - modifying/updating field values, adding additional logic and validation, access control, deleting the record itself, 12 | interacting with other contracts and records et al. 13 | 14 | ## Modifying or Updating records 15 | 16 | Field values of records can only be updated via functions. Consider the following contract, for instance: 17 | 18 | ```typescript 19 | contract Person { 20 | id: string; 21 | name: string; 22 | age: number; 23 | 24 | constructor (id: string, name: string, age: number) { 25 | this.id = id; 26 | this.name = name; 27 | this.age = age; 28 | } 29 | 30 | setName(newName: string) { 31 | this.name = newName; 32 | } 33 | 34 | setAge(newAge: number) { 35 | this.age = newAge; 36 | } 37 | } 38 | ``` 39 | 40 | The `setName` and `setAge` functions can be used to update the values for the `name` and `age` fields respectively. 41 | 42 | ## Validation 43 | 44 | You can have custom validation logic inside functions. In the following example, we check that account we're transferring money from has sufficient funds to carry out that operation: 45 | 46 | ```typescript 47 | contract Account { 48 | id; string; 49 | balance: number; 50 | 51 | constructor (id: string) { 52 | this.id = id; 53 | this.balance = 0; 54 | } 55 | 56 | transfer (to: Account, amount: number) { 57 | // ensure we have enough money in our account 58 | if (this.balance < amount) { 59 | error('Insufficient funds for transfer'); 60 | } 61 | 62 | this.balance -= amount; 63 | to.balance += amount; 64 | } 65 | } 66 | ``` 67 | 68 | ## Access Control 69 | 70 | We can also provide tighter checks on *who* can modify the record. Taking the same `Account` contract as above, we modify like so: 71 | 72 | ```typescript 73 | contract Account { 74 | id; string; 75 | balance: number; 76 | publicKey: PublicKey; 77 | 78 | constructor (id: string) { 79 | this.id = id; 80 | this.publicKey = ctx.publicKey; 81 | this.balance = 0; 82 | } 83 | 84 | transfer (to: Account, amount: number) { 85 | // first check that we are authorized to attempt this transaction 86 | if (this.publicKey != ctx.publicKey) { 87 | error('User is not authorized to transfer funds from this account'); 88 | } 89 | 90 | // ensure we have enough money in our account 91 | if (this.balance < amount) { 92 | error('Insufficient funds for transfer'); 93 | } 94 | 95 | this.balance -= amount; 96 | to.balance += amount; 97 | } 98 | } 99 | ``` 100 | 101 | In the example above, we initialize the `publicKey` field with the public key of the user who created the `Account`, and when the `transfer` function is invoked to transfer funds 102 | out of this account, we check whether the public key of the user attempting this transactions matches that of the user who created this account. 103 | 104 | 105 | ## Deleting a record 106 | 107 | `Polylang` allows deletion of records using the `selfdestruct` **built-in** function. This built-in function must be, of course, called from within a `Polylang` function. This function 108 | can be named anything, but by convention, such a deletion function is called `del`. 109 | 110 | For example (building on the same contract): 111 | 112 | ```typescript 113 | contract Account { 114 | id; string; 115 | balance: number; 116 | publicKey: PublicKey; 117 | 118 | constructor (id: string) { 119 | this.id = id; 120 | this.publicKey = ctx.publicKey; 121 | this.balance = 0; 122 | } 123 | 124 | transfer (to: Account, amount: number) { 125 | // first check that we are authorized to attempt this transaction 126 | if (this.publicKey != ctx.publicKey) { 127 | error('User is not authorized to transfer funds from this account'); 128 | } 129 | 130 | // ensure we have enough money in our account 131 | if (this.balance < amount) { 132 | error('Insufficient funds for transfer'); 133 | } 134 | 135 | this.balance -= amount; 136 | to.balance += amount; 137 | } 138 | 139 | // the deletion function for this contract 140 | del() { 141 | if (this. publicKey != ctx.publicKey) { 142 | error('User is not authorized to delete this account'); 143 | } 144 | 145 | // call the built-in function 146 | selfdestruct(); 147 | } 148 | } 149 | ``` 150 | -------------------------------------------------------------------------------- /site/pages/docs/language-features/general-structure.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Structure of Programs 3 | --- 4 | 5 | # Structure of Programs 6 | 7 | import { Callout } from 'nextra/components' 8 | 9 | A semi-formal representation of `Polylang` programs is shown below. 10 | 11 | 12 | For the official grammar for `Polylang`, please refer to this [grammar file](https://github.com/polybase/polylang/blob/main/parser/src/polylang.lalrpop). 13 | 14 | 15 | ## Grammar Notation 16 | 17 | * `{x}` means zero or more occurrences of `x`. 18 | * `[x]` means zero or one occurrences of `x`, meaning that `x` is optional. 19 | * `(one of)` followed by space-separated items indicates a choice of one of the items. 20 | * `x` indicates a literal of value `x`. 21 | 22 | ## Simplified Grammar for Polylang 23 | 24 | A `polylang` program ("schema") consists of one or more [contract](https://polybase.xyz/docs/collections) declarations: 25 | 26 | ``` 27 | Schema: 28 | [ContractDeclaration] 29 | ``` 30 | 31 | where each contract consists of field declarations, a constructor declaration, and function declarations: 32 | 33 | ```yaml 34 | ContractDeclaration: 35 | [(one of)`@public` `@private`] 36 | `contract` ContractName ContractBody 37 | 38 | ContractName: 39 | alphanumeric starting with a letter 40 | 41 | ContractBody: 42 | {FieldDeclaration} 43 | [ConstructorDeclaration] 44 | {FunctionDeclaration} 45 | ``` 46 | 47 | ```yaml 48 | FieldDeclaration: 49 | (one of) 50 | RequiredFieldDeclaration OptionalFieldDeclaration 51 | 52 | RequiredFieldDeclaration: 53 | FieldName: FieldType ';' 54 | 55 | OptionalFieldDeclaration: 56 | FieldName`?` : FieldTyoe ';' 57 | 58 | FieldName: 59 | alphanumeric starting with a letter 60 | ``` 61 | 62 | where each field can have the following types: 63 | 64 | ```yaml 65 | FieldType: 66 | (one of) 67 | PrimitiveType `bytes` `PublicKey` ContractType ArrayType MapType ObjectType 68 | 69 | PrimitiveType: 70 | (one of) 71 | `string` `number` `boolean` 72 | 73 | ContractType: 74 | a reference to a contract 75 | 76 | ArrayType: 77 | `string`[] `number`[] `boolean`[] `PublicKey`[] ContractType[] 78 | 79 | MapType: 80 | `map` `<`MapKeyType : FieldType `>` 81 | 82 | MapKeyType: 83 | (one of) 84 | `string` `number` 85 | 86 | ObjectType: 87 | '{' [FieldName ':' FieldType] '}' 88 | ``` 89 | 90 | Field types are defined in more detail [here](fieldtypes). 91 | 92 | Constructors and functions are similar in structure. Note that the `constructor` keyword is mandatory for a constructor declaration: 93 | 94 | ```yaml 95 | ConstructorDeclaration: 96 | `constructor` ConstructorName ConstructorArgs ConstructorBody 97 | 98 | ConstructorName: 99 | alphanumeric starting with a letter 100 | 101 | ConstructorArgs: 102 | '(' [ArgPair] {`,` ArgPair} ')' 103 | ``` 104 | 105 | ```yaml 106 | ArgPair: 107 | ArgName ':' ArgType 108 | 109 | ArgName: 110 | alphanumeric starting with a letter 111 | 112 | ArgType: 113 | FieldType 114 | ``` 115 | 116 | Note that the `function` keyword in a function declaration is optional: 117 | 118 | ```yaml 119 | FunctionDeclaration: 120 | [`function`] FunctionName FunctionArgs FunctionBody 121 | 122 | FunctionName: 123 | alphanumeric starting with a letter 124 | 125 | FunctionArgs: 126 | ConstructorArgs 127 | 128 | FunctionBody: 129 | ConstructorBody 130 | ``` 131 | -------------------------------------------------------------------------------- /site/pages/docs/language-features/lexical-structure.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Lexical Structure 3 | --- 4 | 5 | import { Callout } from 'nextra/components' 6 | 7 | # Lexical Structure 8 | 9 | ## Comments 10 | 11 | `Polylang` supports both singl-line comments using `//` extending to the end of the line: 12 | 13 | ```typescript 14 | contract Computer { 15 | id: string; 16 | // the model for this computer 17 | model: string; 18 | 19 | // create an instance of the `Computer` contract 20 | constructor (id: string, model: string) { 21 | this.id = id; 22 | this.model = model; 23 | } 24 | } 25 | ``` 26 | 27 | as well as multi-line comments using `/* ... */`: 28 | 29 | ```typescript 30 | contract Computer { 31 | id: string; 32 | // the model for this computer 33 | model: string; 34 | 35 | // create an instance of the `Computer` contract 36 | constructor (id: string, model: string) { 37 | this.id = id; 38 | this.model = model; 39 | } 40 | 41 | /* 42 | Update the model for this particulat computer. 43 | Changes the value of the `model` field. 44 | */ 45 | setModel(newModel: string) { 46 | this.model = newModel; 47 | } 48 | } 49 | ``` 50 | 51 | ## Identifiers 52 | 53 | An identifier is an unlimited-length sequence of letters and digits, the first of which must be a letter (or underscore). Identifiers are used for naming contracts, fields, and functions. 54 | Identifiers may not be a [reserved word](#reserved-words). 55 | 56 | ## Literals 57 | 58 | `Polylang` supports the following literal types: 59 | * String literals - a sequence of Unicode (`UTF-8`) characters. Eg: `"Hello, мир"`. 60 | * Numeric literals: 61 | - the `number` type supports both integral and decimal values. Eg: `42`, `-12.3849`. This is like the `number` type in `JavaScript`/`TypeScript`. 62 | - the named integral and decimal types support more restricted values. Eg: 63 | * i32 - 32-bit signed integers such as `42`, `-2356`. 64 | * u32 - 32-bit unsigned integers such as `0`, `21`. 65 | * Boolean literals - `true` and `false`. 66 | 67 | ## Operators 68 | 69 | `Polylang` supports the usual arithmetic, relational, assignment, and boolean operators: 70 | * `*`, `/`, `%`, `+`, `-` 71 | * `-=`, `+=` 72 | * `&`, `**`, `&&`, `^`, `|`, `||`, `<=`, `>=`, `=`, `==`, `!=`, 73 | 74 | ## Reserved Words 75 | 76 | The following are reserved for use as keywords, and cannot be used as identifiers in `Polylang` (contract names, field names, function/method names etc.): 77 | * `true`, `false` 78 | * `number`, `f32`, `f64`, `u32`, `u64`, `i32`, `i64` 79 | * `string` 80 | * `boolean` 81 | * `map` 82 | * `record` 83 | * `let` 84 | * `break`, `return`, `throw` 85 | * `if`, `else` 86 | * `while`, `for` 87 | * `in`, `of` 88 | * `function` 89 | * `collection` 90 | * `contract` 91 | * `PublicKey` 92 | * `bytes` 93 | 94 | 95 | JavaScript reserved words **cannot** be used identifiers. Please refer to [this list](https://tc39.es/ecma262/#sec-keywords-and-reserved-words) for an exhaustive list of forbidden names. 96 | 97 | -------------------------------------------------------------------------------- /site/pages/docs/zk-proofs/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "what-is-zk": "Overview of Zero-Knowledge Proofs (ZKP)", 3 | "polylang-and-zk": "Polylang and Zero-Knowledge" 4 | } -------------------------------------------------------------------------------- /site/pages/docs/zk-proofs/polylang-and-zk.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Polylang and Zero-Knowledge 3 | --- 4 | 5 | # Polylang and Zero-Knowledge 6 | 7 | import { Callout } from 'nextra/components'; 8 | 9 | To run `Polylang` programs on the Miden VM, we follow a two step process: 10 | 1. Compile the program into Miden assembly. 11 | 2. Run the program by invoking `miden-run` and passing in: 12 | * the output of the compilation step (produced in step 1) 13 | * the input contract (if applicable) using the `--this-json` flag 14 | * the inputs for the code (if applicable) using the `--advice-tape-json` flag 15 | 16 | A number of showcase examples follow. 17 | 18 | 19 | The `Polylang` CLI is being actively worked on, and ergonomics should improve in future iterations. 20 | 21 | 22 | ## Minimal example 23 | 24 | Consider a minimal `Polylang` program: 25 | 26 | ```typescript 27 | function main() { 28 | } 29 | ``` 30 | 31 | This program simply has a function, `main` that does nothing. 32 | 33 | Compile and extract the ABI string: 34 | 35 | ```bash 36 | $ cargo run --bin compile -- function:main <<<'function main() {}' | cargo run -p miden-run 37 | ``` 38 | 39 | Output: 40 | 41 | ```bash 42 | 43 | ``` 44 | 45 | ## Adding numbers 46 | 47 | The use case is that we wish to add two numbers which are supplied to our program, and calculate the sum. 48 | 49 | Define the contract: 50 | 51 | ```typescript 52 | contract AddNums { 53 | sum: number; // this will store the sum 54 | 55 | addNums(a: number, b: number) { 56 | this.sum = a + b; 57 | } 58 | } 59 | ``` 60 | 61 | Compile and run the program passing the numbers `1` and `2` as inputs: 62 | 63 | ```bash 64 | $ cargo run --bin compile -- contract:AddNums function:addNums <<< 'contract AddNums { sum: number; addNums(a: number, b: number) { this.sum = a + b; }}' | \ 65 | cargo run -p miden-run -- --this-json '{ "sum": 0 }' \ 66 | --advice-tape-json '[1, 2]' 67 | ``` 68 | 69 | The output: 70 | 71 | ```bash 72 | 73 | this_json: {"sum":3} 74 | ``` 75 | 76 | ## A full contract example 77 | 78 | Consider the contract: 79 | 80 | ```typescript 81 | contract Person { 82 | id: string; 83 | name: string; 84 | age: number; 85 | 86 | constructor (id: string, name: string, age: number) { 87 | this.id = id; 88 | this.name = name; 89 | this.age = age; 90 | } 91 | 92 | setName(newName: string) { 93 | this.name = newName; 94 | } 95 | 96 | setAge(newAge: number) { 97 | this.age = newAge; 98 | } 99 | 100 | del () { 101 | selfdestruct(); 102 | } 103 | } 104 | ``` 105 | 106 | Suppose we have a record with the following data: 107 | 108 | ```json 109 | { 110 | "id": "id1", 111 | "name": "Bob", 112 | "age": 19 113 | } 114 | ``` 115 | 116 | and we wish to update the `age` to 20. So we have to invoke the `setAge` function/method on the `Person` contract for this record. 117 | 118 | Compile and run the program and pass in 20 for the new `age`: 119 | 120 | ```bash 121 | $ cargo run --bin compile -- contract:Person function:setAge <<< \ 122 | ' 123 | contract Person { 124 | id: string; 125 | name: string; 126 | age: number; 127 | 128 | constructor (id: string, name: string, age: number) { 129 | this.id = id; 130 | this.name = name; 131 | this.age = age; 132 | } 133 | 134 | setName(newName: string) { 135 | this.name = newName; 136 | } 137 | 138 | setAge(newAge: number) { 139 | this.age = newAge; 140 | } 141 | 142 | del () { 143 | selfdestruct(); 144 | } 145 | } 146 | ' | \ 147 | cargo run -p miden-run -- --this-json '{ "id": "id1", "name": "Bob", "age": 19 }' \ 148 | --advice-tape-json '[20]' 149 | ``` 150 | 151 | The output: 152 | 153 | ```bash 154 | 155 | this_json: {"age":20,"id":"id1","name":"Bob"} 156 | ``` 157 | 158 | ## So where is the proof? 159 | 160 | If a `Polylang` program runs to completion, then the code is provably correct. If we still wish to generate an explicit proof output, we can pass the `--proof-output` flag to the 161 | `miden-run` executable. 162 | 163 | The syntax is: 164 | 165 | ```bash 166 | $ cargo run -p miden-run -- --proof-output 167 | ``` 168 | 169 | Here is how we could generate the proof for the [first example](#minimal-example): 170 | 171 | ```bash 172 | $ cargo run --bin compile -- function:main <<<'function main() {}' | cargo run -p miden-run -- --proof-output minimal.proof 173 | ``` 174 | 175 | Output: 176 | 177 | ```bash 178 | $ file minimal.proof 179 | minimal.proof: data 180 | 181 | $ xxd minimal.proof | head 182 | 00000000: 0048 0910 0a00 0008 0100 0000 ffff ffff .H.............. 183 | 00000010: 1b08 1002 08ff 7800 8e5c d6e1 b263 696d ......x..\...cim 184 | 00000020: 168a c6b8 fefb 1b5d c315 4663 9d24 c477 .......]..Fc.$.w 185 | 00000030: c829 cf37 23f8 bf7a 7a94 21fd f47c fada .).7#..zz.!..|.. 186 | 00000040: 48b0 9bd6 6520 4a57 1e5d 7c2f 9a56 c5a0 H...e JW.]|/.V.. 187 | 00000050: 56f6 3d96 9e89 8a47 b4f7 c72b c9da b9fa V.=....G...+.... 188 | 00000060: 473c 90f5 faa0 bda1 e61f 09d4 3875 4a20 G<..........8uJ 189 | 00000070: 07a5 b83c 989e 9bc7 56bf 08a2 80fa b2e1 ...<....V....... 190 | 00000080: 439b 18a8 1ab6 b453 8596 df8b d458 4865 C......S.....XHe 191 | 00000090: c03c 0000 7364 28c6 c965 2965 6816 bcda .<..sd(..e)eh... 192 | ``` 193 | 194 | The proof is stored as a sequence of bytes in the `minimal.proof` file. 195 | 196 | 197 | `Polylang` does not provide an option for verification via the CLI, but an external library such as [Miden Verifier](https://crates.io/crates/miden-verifier) may be used to explicitly verify the proof. 198 | 199 | -------------------------------------------------------------------------------- /site/pages/docs/zk-proofs/what-is-zk.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview of Zero-Knowledge Proofs (ZKP) 3 | --- 4 | 5 | # Overview of Zero-Knowledge Proofs (ZKP) 6 | 7 | ## What is a Zero-Knowledge Proof/Protocol? 8 | 9 | A Zero-Knowledge Proof or a Zero-Knowledge Protocol (ZKP) is a way of proving the validity of a statement without revealing the statement itself. Only the fact of the statement being 10 | valid (i.e., true) is sufficient for a part (the Verifier) to validate the claims made by the other party (the Prover). Hence "Zero-Knowledge" since the statement itself is not part of 11 | the verification process. 12 | 13 | There are two main aspects for Zero-Knowledge protocols: 14 | 1. Zero Knowledge - that no data other than the claim of of the validity of a statement is shared between the Prover and the Verifier. 15 | 2. Provable Computation - the ability to validate that the computation ran as designed, and so the output of the computation can be trusted. 16 | 17 | Zero-Knowledge protocols can be interactive or non-interactive. For most practical applications, non-interactive protocols are preferred. These can be classified into two 18 | main categories: 19 | 20 | * ZK-SNARKs (Zero-Knowledge Succinct Non-interactve Arguments of Knowledge). 21 | * ZK-STARKs (Zero-Knowledge Scalable Transparent Argument of Knowledge). 22 | 23 | For more details on ZKPs, and a comparison of zk-SNARKs vs zk-STARKS, please refer to the [zk-benchmarks website](https://zk-benchmarks.vercel.app/) along with the links in the [references](#references). 24 | 25 | ## What does Polylang use? 26 | 27 | `Polylang` uses zk-STARK. `Polylang` programs are compiled to [Miden VM](https://github.com/0xPolygonMiden/miden-vm) assembly instructions, and proofs are generated on Miden. Miden is a zk-STARK based virtual 28 | machine. For more details on Miden, please refer to their [official documentation](https://wiki.polygon.technology/docs/miden/user_docs/usage/). 29 | 30 | ## How does Polylang use Zero-Knowledge? 31 | 32 | The lifecycle of a `Polylang` program is: 33 | 34 | ```mermaid 35 | flowchart TD 36 | PolylangProgram[Polylang Program] --(Parser) parse--> AST[Abstract Syntax Tree] 37 | --(Compiler) compile--> CompilerOutput[Miden Assembly Code, Polylang ABI] 38 | --(Prover) hash contract fields--> Inputs[Miden Inputs] 39 | --(Prover) compile--> Program[Miden Program] 40 | --(Prover) run on Miden VM --> ProverOutput[Output, Proof] 41 | ``` 42 | 43 | The `Polylang` compiler compiles the `Polylang` contract and program and *generates* code that can be verified and run on the Miden VM during the run stage. This includes generating code 44 | to verify fields and functions using hashes, validating the inputs, and [Declarative Permissions](/docs/language-features/permissions). 45 | 46 | Zooming in on the `Polylang` compiler compilaton stage: 47 | 48 | ```mermaid 49 | flowchart TD 50 | AST[Abstract Syntax Tree] -- calculate contract fields' hashes --> Intermediate1 51 | -- generate authorization checks based on the context's public key --> Intermediate2 52 | -- generate code for other checks --> Intermediate3 53 | --> CompilerOutput[Miden VM Assembly Code, Polylang ABI] 54 | ``` 55 | 56 | The `Polylang` prover uses the available context from compilation for actual verification, running, and proof generation: 57 | 58 | ```mermaid 59 | flowchart TD 60 | Program[Miden Program] --match actual (input) field hashes against calculated field hashes during compilation--> Intermediate1 61 | -- verify authorization checks based on the public key --> Intermediate2 62 | -- other checks --> Intermediate3 63 | --> ProverOutput[Program run Output, Proof] 64 | 65 | ``` 66 | 67 | ## References 68 | 69 | * https://ethereum.org/en/zero-knowledge-proofs/ 70 | * https://en.wikipedia.org/wiki/Non-interactive_zero-knowledge_proof 71 | * https://blog.chain.link/zk-snarks-vs-zk-starks/ -------------------------------------------------------------------------------- /site/pages/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Polylang 3 | --- 4 | 5 | import Home from '../components/Home' 6 | 7 | 8 | 9 | ## What is zero knowledge (ZK)? 10 | 11 | ZK (zero knowledge) is a type of cryptography that allows a proof (a kind of certificate) to be generated that allows one party to prove to another that they know a value (or that some condition holds true), without revealing any information about the value itself. 12 | 13 | There are two main components to ZK: 14 | 15 | 1. **Zero Knowledge** - the ability to run computation without revealing all of the inputs, but having the output be trusted 16 | 17 | 2. **Provable computation** - proves the code ran as designed, and therefore the output can be trusted 18 | 19 | Here's a simple example to demonstrate the idea of provable computation. Below the code checks if the input provided is over 18. 20 | If we ran this in zero knowledge, we could generate a proof that the input value was > 18 without revealing the actual number/age: 21 | 22 | ```js 23 | function isOver18(age: number): boolean { 24 | return age > 18; 25 | } 26 | ``` 27 | 28 | Of course, this is much more useful if we have some kind commitment scheme where we can verify the age also belongs to a given user. For example, a digital credential provided by the government. 29 | 30 | 31 | ## Why is it useful? 32 | 33 | There are two main use cases for zero knowledge: 34 | 35 | 1. **Privacy** - when combined with a commitment scheme, you can enforce a set of rules for "valid state" without having to see what the underlying state is. This can be useful in preserving privacy. 36 | 37 | 1. **Scalability** - if you have many parties that need to agree on the outcome of some computation, it may be more efficient for one computer to compute the outcome, and then every other node can be sure of the outcome. 38 | 39 | As you can imagine these properties are very useful in decentralised compute and blockchains. 40 | 41 | 42 | ## What is a commitment scheme? 43 | 44 | A commitment scheme is a way to commit to some state, without revealing what that state is. This is often achieved using [hashes](https://www.sentinelone.com/cybersecurity-101/hashing/) (that represent the state of a specific record or value) and merkle trees (combining multiple hash commitments into a single hash commitment). When combined with zero knowledge, commitment schemes allow developers to define the rules of a system in zero knowledge and then use a commitment scheme to enforce valid state transitions (i.e. you cannot just increase your balance). 45 | 46 | Polylang automatically hashes the inputs, verifies that the input data matches an existing hash, outputs the value, along with its corresponding hash. This makes it simple to store a commitment to the values to be used in the next state transition. 47 | 48 | 49 | ## How performant is it? 50 | 51 | We also made [ZK Bench](https://zkbench.dev) while developing Polylang benchmarking site to compare the performance of various ZK frameworks and tools - and of course we included Polylang in that! Keep in mind that Polylang is doing a bunch of extra steps to hash and validate the commitments for you. 52 | 53 | -------------------------------------------------------------------------------- /site/pages/playground.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Playground 3 | --- 4 | 5 | import Playground from '../components/Playground' 6 | 7 | -------------------------------------------------------------------------------- /site/public/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /site/public/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polybase/polylang/5257cb206079fbbd2e7244359895eac9bd300e1b/site/public/social.png -------------------------------------------------------------------------------- /site/theme.config.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { DocsThemeConfig } from 'nextra-theme-docs' 3 | import { Logo } from './components/Logo' 4 | 5 | const config: DocsThemeConfig = { 6 | logo: , 7 | primaryHue: 314, 8 | project: { 9 | link: 'https://github.com/polybase/polylang/', 10 | }, 11 | chat: { 12 | link: 'https://discord.com/invite/DrXkRpCFDX', 13 | }, 14 | docsRepositoryBase: 'https://github.com/polybase/polylang/tree/main/site/', 15 | banner: { 16 | key: '2.0-release', 17 | text: ( 18 | 19 | 🎉 We're hiring ZK (Halo2) developers. Read more → 20 | 21 | ) 22 | }, 23 | useNextSeoProps() { 24 | return { 25 | titleTemplate: '%s – Polylang by Polybase Labs' 26 | } 27 | }, 28 | head: ( 29 | <> 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ), 38 | footer: { 39 | text: 'Polylang Docs', 40 | }, 41 | } 42 | 43 | export default config 44 | -------------------------------------------------------------------------------- /site/theme.ts: -------------------------------------------------------------------------------- 1 | import { extendTheme } from '@chakra-ui/react' 2 | 3 | const mode = (light: any, _dark: any) => ({ default: light, _dark }) 4 | 5 | export const theme = extendTheme({ 6 | semanticTokens: { 7 | colors: { 8 | error: 'red.500', 9 | warning: mode('#ca4b03c7', '#cc630887'), 10 | 'bws': mode('rgba(255, 255, 255)', 'rgba(26, 32, 44)'), 11 | 'bws.100': mode('rgba(240, 240, 240)', 'rgba(29, 31, 36)'), 12 | 'bw.10': mode('rgba(0, 0, 0, 0.01)', 'rgba(255, 255, 255, 0.01)'), 13 | 'bw.50': mode('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.04)'), 14 | 'bw.100': mode('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.06)'), 15 | 'bw.200': mode('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.08)'), 16 | 'bw.300': mode('rgba(0, 0, 0, 0.16)', 'rgba(255, 255, 255, 0.16)'), 17 | 'bw.400': mode('rgba(0, 0, 0, 0.24)', 'rgba(255, 255, 255, 0.24)'), 18 | 'bw.500': mode('rgba(0, 0, 0, 0.36)', 'rgba(255, 255, 255, 0.36)'), 19 | 'bw.600': mode('rgba(0, 0, 0, 0.48)', 'rgba(255, 255, 255, 0.48)'), 20 | 'bw.700': mode('rgba(0, 0, 0, 0.64)', 'rgba(255, 255, 255, 0.64)'), 21 | 'bw.800': mode('rgba(0, 0, 0, 0.80)', 'rgba(255, 255, 255, 0.80)'), 22 | 'bw.900': mode('rgba(0, 0, 0, 0.92)', 'rgba(255, 255, 255, 0.92)'), 23 | }, 24 | }, 25 | }) -------------------------------------------------------------------------------- /site/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "incremental": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve" 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /src/bin/compile/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | fn main() { 4 | let mut code = String::new(); 5 | std::io::stdin().read_to_string(&mut code).unwrap(); 6 | 7 | let mut contract_name = None; 8 | let mut function_name = "main".to_string(); 9 | 10 | for arg in std::env::args().skip(1) { 11 | match arg.split_once(':') { 12 | Some((key, value)) => match key { 13 | "contract" => contract_name = Some(value.to_string()), 14 | "function" => function_name = value.to_string(), 15 | _ => panic!("unknown argument: {}", key), 16 | }, 17 | None => panic!("invalid argument: {}", arg), 18 | } 19 | } 20 | 21 | let program = polylang_parser::parse(&code).unwrap(); 22 | 23 | let (miden_code, abi) = 24 | polylang::compiler::compile(program, contract_name.as_deref(), &function_name) 25 | .map_err(|e| e.add_source(code)) 26 | .unwrap_or_else(|e| panic!("{e}")); 27 | println!("{}", miden_code); 28 | eprintln!("ABI: {}", serde_json::to_string(&abi).unwrap()); 29 | } 30 | -------------------------------------------------------------------------------- /src/bindings.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::c_char; 2 | #[cfg(target_arch = "wasm32")] 3 | use wasm_bindgen::prelude::*; 4 | 5 | #[cfg(target_arch = "wasm32")] 6 | #[wasm_bindgen] 7 | extern "C" { 8 | #[wasm_bindgen(js_namespace = console)] 9 | fn error(msg: String); 10 | } 11 | 12 | #[cfg(target_arch = "wasm32")] 13 | #[wasm_bindgen] 14 | pub extern "C" fn init() { 15 | std::panic::set_hook(Box::new(console_error_panic_hook::hook)); 16 | } 17 | 18 | #[cfg(target_arch = "wasm32")] 19 | #[cfg(feature = "parser")] 20 | #[wasm_bindgen] 21 | pub fn parse(input: &str, namespace: &str) -> String { 22 | crate::parse_out_json(input, namespace) 23 | } 24 | 25 | #[cfg(target_arch = "wasm32")] 26 | #[wasm_bindgen] 27 | pub fn validate_set(ast_json: &str, data_json: &str) -> String { 28 | crate::validate_set_out_json(ast_json, data_json) 29 | } 30 | 31 | #[cfg(target_arch = "wasm32")] 32 | #[wasm_bindgen] 33 | pub fn generate_js_contract(contract_ast_json: &str) -> String { 34 | crate::generate_js_contract_out_json(contract_ast_json) 35 | } 36 | 37 | #[cfg(not(target_arch = "wasm32"))] 38 | #[cfg(feature = "parser")] 39 | #[no_mangle] 40 | pub extern "C" fn parse(input: *const c_char, namespace: *const c_char) -> *mut c_char { 41 | let input = unsafe { std::ffi::CStr::from_ptr(input) }; 42 | let input = input.to_str().unwrap(); 43 | 44 | let namespace = unsafe { std::ffi::CStr::from_ptr(namespace) }; 45 | let namespace = namespace.to_str().unwrap(); 46 | 47 | let output = crate::parse_out_json(input, namespace); 48 | let output = std::ffi::CString::new(output).unwrap(); 49 | output.into_raw() 50 | } 51 | 52 | #[cfg(not(target_arch = "wasm32"))] 53 | #[no_mangle] 54 | pub extern "C" fn validate_set(ast_json: *const c_char, data_json: *const c_char) -> *mut c_char { 55 | let ast_json = unsafe { std::ffi::CStr::from_ptr(ast_json) }; 56 | let ast_json = ast_json.to_str().unwrap(); 57 | 58 | let data_json = unsafe { std::ffi::CStr::from_ptr(data_json) }; 59 | let data_json = data_json.to_str().unwrap(); 60 | 61 | let output = crate::validate_set_out_json(ast_json, data_json); 62 | let output = std::ffi::CString::new(output).unwrap(); 63 | output.into_raw() 64 | } 65 | 66 | #[cfg(not(target_arch = "wasm32"))] 67 | #[no_mangle] 68 | pub extern "C" fn generate_js_contract(contract_ast_json: *const c_char) -> *mut c_char { 69 | let contract_ast_json = unsafe { std::ffi::CStr::from_ptr(contract_ast_json) }; 70 | let contract_ast_json = contract_ast_json.to_str().unwrap(); 71 | 72 | let output = crate::generate_js_contract_out_json(contract_ast_json); 73 | let output = std::ffi::CString::new(output).unwrap(); 74 | output.into_raw() 75 | } 76 | -------------------------------------------------------------------------------- /src/compiler/boolean.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | // Layout: [value] - where value is 0 or 1 4 | pub(crate) fn new(compiler: &mut Compiler, value: bool) -> Symbol { 5 | let symbol = compiler 6 | .memory 7 | .allocate_symbol(Type::PrimitiveType(PrimitiveType::Boolean)); 8 | 9 | // memory is zero-initialized, so we don't need to write for false 10 | if value { 11 | compiler.memory.write( 12 | compiler.instructions, 13 | symbol.memory_addr, 14 | &[ValueSource::Immediate(1)], 15 | ); 16 | } 17 | 18 | symbol 19 | } 20 | 21 | pub(crate) fn compile_and(compiler: &mut Compiler, a: &Symbol, b: &Symbol) -> Symbol { 22 | assert_eq!(a.type_, b.type_); 23 | assert_eq!(a.type_, Type::PrimitiveType(PrimitiveType::Boolean)); 24 | 25 | let result = compiler 26 | .memory 27 | .allocate_symbol(Type::PrimitiveType(PrimitiveType::Boolean)); 28 | compiler 29 | .memory 30 | .read(compiler.instructions, a.memory_addr, a.type_.miden_width()); 31 | compiler 32 | .memory 33 | .read(compiler.instructions, b.memory_addr, b.type_.miden_width()); 34 | compiler.instructions.push(encoder::Instruction::And); 35 | compiler.memory.write( 36 | compiler.instructions, 37 | result.memory_addr, 38 | &[ValueSource::Stack], 39 | ); 40 | 41 | result 42 | } 43 | 44 | pub(crate) fn compile_or(compiler: &mut Compiler, a: &Symbol, b: &Symbol) -> Symbol { 45 | assert_eq!(a.type_, b.type_); 46 | assert_eq!(a.type_, Type::PrimitiveType(PrimitiveType::Boolean)); 47 | 48 | let result = compiler 49 | .memory 50 | .allocate_symbol(Type::PrimitiveType(PrimitiveType::Boolean)); 51 | compiler 52 | .memory 53 | .read(compiler.instructions, a.memory_addr, a.type_.miden_width()); 54 | compiler 55 | .memory 56 | .read(compiler.instructions, b.memory_addr, b.type_.miden_width()); 57 | compiler.instructions.push(encoder::Instruction::Or); 58 | compiler.memory.write( 59 | compiler.instructions, 60 | result.memory_addr, 61 | &[ValueSource::Stack], 62 | ); 63 | 64 | result 65 | } 66 | -------------------------------------------------------------------------------- /src/compiler/bytes.rs: -------------------------------------------------------------------------------- 1 | // Layout: [len, data_ptr] 2 | -------------------------------------------------------------------------------- /src/compiler/float64.rs: -------------------------------------------------------------------------------- 1 | // Layout: [high, low] 2 | -------------------------------------------------------------------------------- /src/compiler/int64.rs: -------------------------------------------------------------------------------- 1 | // Layout: [high, low] 2 | -------------------------------------------------------------------------------- /src/compiler/nullable.rs: -------------------------------------------------------------------------------- 1 | use super::{encoder::Instruction, *}; 2 | 3 | // [is_not_null, t_elements...] 4 | #[allow(dead_code)] 5 | pub(crate) fn width(t: &Type) -> u32 { 6 | 1 + t.miden_width() 7 | } 8 | 9 | pub(crate) fn is_not_null(value: &Symbol) -> Symbol { 10 | Symbol { 11 | memory_addr: value.memory_addr, 12 | type_: Type::PrimitiveType(PrimitiveType::Boolean), 13 | } 14 | } 15 | 16 | pub(crate) fn value(value: Symbol) -> Symbol { 17 | Symbol { 18 | memory_addr: value.memory_addr + 1, 19 | type_: match value.type_ { 20 | Type::Nullable(t) => *t, 21 | _ => panic!("value is not nullable"), 22 | }, 23 | } 24 | } 25 | 26 | pub(crate) fn eq(compiler: &mut Compiler, a: &Symbol, b: &Symbol) -> Symbol { 27 | let result = compiler 28 | .memory 29 | .allocate_symbol(Type::PrimitiveType(PrimitiveType::Boolean)); 30 | 31 | let (inner_type_eq_result, inner_type_eq_insts) = { 32 | let mut insts = vec![]; 33 | std::mem::swap(compiler.instructions, &mut insts); 34 | 35 | let res = compile_eq(compiler, &value(a.clone()), &value(b.clone())).unwrap(); 36 | 37 | std::mem::swap(compiler.instructions, &mut insts); 38 | (res, insts) 39 | }; 40 | 41 | compiler.instructions.extend([ 42 | // if (a.is_not_null && b.is_not_null) { 43 | Instruction::If { 44 | condition: vec![ 45 | Instruction::MemLoad(Some(is_not_null(a).memory_addr)), 46 | Instruction::MemLoad(Some(is_not_null(b).memory_addr)), 47 | Instruction::And, 48 | ], 49 | // return a.value == b.value 50 | then: inner_type_eq_insts 51 | .into_iter() 52 | .chain([ 53 | Instruction::MemLoad(Some(inner_type_eq_result.memory_addr)), 54 | Instruction::MemStore(Some(result.memory_addr)), 55 | ]) 56 | .collect(), 57 | // else { return a.is_not_null == b.is_not_null } 58 | else_: vec![ 59 | Instruction::MemLoad(Some(is_not_null(a).memory_addr)), 60 | Instruction::MemLoad(Some(is_not_null(b).memory_addr)), 61 | Instruction::Eq, 62 | Instruction::MemStore(Some(result.memory_addr)), 63 | ], 64 | }, 65 | ]); 66 | 67 | result 68 | } 69 | -------------------------------------------------------------------------------- /src/js.rs: -------------------------------------------------------------------------------- 1 | use crate::stableast; 2 | use serde::Serialize; 3 | 4 | #[derive(Debug, Serialize, PartialEq)] 5 | pub struct JSContract { 6 | pub code: String, 7 | } 8 | 9 | pub fn generate_js_contract(contract_ast: &stableast::Contract) -> JSContract { 10 | let fns = contract_ast 11 | .attributes 12 | .iter() 13 | .filter_map(|item| { 14 | if let stableast::ContractAttribute::Method(m) = item { 15 | let JSFunc { name, code } = generate_js_function(m); 16 | Some(format!("instance.{} = {}", &name, &code)) 17 | } else { 18 | None 19 | } 20 | }) 21 | .collect::>() 22 | .join(";"); 23 | 24 | JSContract { 25 | code: format!( 26 | "function error(str) {{ 27 | return new Error(str); 28 | }} 29 | 30 | const instance = $$__instance; 31 | {};", 32 | fns, 33 | ), 34 | } 35 | } 36 | 37 | #[derive(Debug, PartialEq)] 38 | struct JSFunc { 39 | name: String, 40 | code: String, 41 | } 42 | 43 | fn generate_js_function(func_ast: &stableast::Method) -> JSFunc { 44 | let parameters = func_ast 45 | .attributes 46 | .iter() 47 | .filter_map(|item| { 48 | if let stableast::MethodAttribute::Parameter(p) = item { 49 | Some(p) 50 | } else { 51 | None 52 | } 53 | }) 54 | .map(|p| format!("{}", p.name)) 55 | .collect::>() 56 | .join(", "); 57 | 58 | JSFunc { 59 | name: func_ast.name.to_string(), 60 | code: format!( 61 | "function {} ({}) {{\n{}\n}}", 62 | func_ast.name, parameters, &func_ast.code, 63 | ), 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | 71 | #[test] 72 | fn test_generate_js_function() { 73 | let func_ast = stableast::Method { 74 | name: "HelloWorld".into(), 75 | attributes: vec![ 76 | stableast::MethodAttribute::Parameter(stableast::Parameter { 77 | name: "a".into(), 78 | type_: stableast::Type::Primitive(stableast::Primitive { 79 | value: stableast::PrimitiveType::String, 80 | }), 81 | required: true, 82 | }), 83 | stableast::MethodAttribute::Parameter(stableast::Parameter { 84 | name: "b".into(), 85 | type_: stableast::Type::Primitive(stableast::Primitive { 86 | value: stableast::PrimitiveType::Number, 87 | }), 88 | required: false, 89 | }), 90 | stableast::MethodAttribute::ReturnValue(stableast::ReturnValue { 91 | name: "_".into(), 92 | type_: stableast::Type::Primitive(stableast::Primitive { 93 | value: stableast::PrimitiveType::String, 94 | }), 95 | }), 96 | ], 97 | code: "return a".into(), 98 | }; 99 | 100 | assert_eq!( 101 | generate_js_function(&func_ast), 102 | JSFunc { 103 | name: "HelloWorld".to_string(), 104 | code: "function HelloWorld (a, b) {\nreturn a\n}".to_string(), 105 | } 106 | ) 107 | } 108 | 109 | #[test] 110 | fn test_generate_contract_function() { 111 | let contract_ast = stableast::Contract { 112 | namespace: stableast::Namespace { value: "".into() }, 113 | name: "ContractName".into(), 114 | attributes: vec![ 115 | stableast::ContractAttribute::Property(stableast::Property { 116 | name: "abc".into(), 117 | type_: stableast::Type::Primitive(stableast::Primitive { 118 | value: stableast::PrimitiveType::String, 119 | }), 120 | directives: vec![], 121 | required: true, 122 | }), 123 | stableast::ContractAttribute::Method(stableast::Method { 124 | name: "Hello".into(), 125 | attributes: vec![ 126 | stableast::MethodAttribute::Parameter(stableast::Parameter { 127 | name: "a".into(), 128 | type_: stableast::Type::Primitive(stableast::Primitive { 129 | value: stableast::PrimitiveType::String, 130 | }), 131 | required: true, 132 | }), 133 | stableast::MethodAttribute::Parameter(stableast::Parameter { 134 | name: "b".into(), 135 | type_: stableast::Type::Primitive(stableast::Primitive { 136 | value: stableast::PrimitiveType::Number, 137 | }), 138 | required: false, 139 | }), 140 | stableast::MethodAttribute::ReturnValue(stableast::ReturnValue { 141 | name: "_".into(), 142 | type_: stableast::Type::Primitive(stableast::Primitive { 143 | value: stableast::PrimitiveType::String, 144 | }), 145 | }), 146 | ], 147 | code: "return a".into(), 148 | }), 149 | stableast::ContractAttribute::Method(stableast::Method { 150 | name: "World".into(), 151 | attributes: vec![ 152 | stableast::MethodAttribute::Parameter(stableast::Parameter { 153 | name: "c".into(), 154 | type_: stableast::Type::Primitive(stableast::Primitive { 155 | value: stableast::PrimitiveType::String, 156 | }), 157 | required: true, 158 | }), 159 | stableast::MethodAttribute::Parameter(stableast::Parameter { 160 | name: "d".into(), 161 | type_: stableast::Type::Primitive(stableast::Primitive { 162 | value: stableast::PrimitiveType::Number, 163 | }), 164 | required: false, 165 | }), 166 | stableast::MethodAttribute::ReturnValue(stableast::ReturnValue { 167 | name: "_".into(), 168 | type_: stableast::Type::Primitive(stableast::Primitive { 169 | value: stableast::PrimitiveType::String, 170 | }), 171 | }), 172 | ], 173 | code: "return c".into(), 174 | }), 175 | ], 176 | }; 177 | 178 | assert_eq!( 179 | generate_js_contract(&contract_ast), 180 | JSContract{ 181 | code: "function error(str) { 182 | return new Error(str); 183 | } 184 | 185 | const instance = $$__instance; 186 | instance.Hello = function Hello (a, b) {\nreturn a\n};instance.World = function World (c, d) {\nreturn c\n};".to_string() 187 | } 188 | ) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail 4 | 5 | cargo build --bin compile 2>/dev/null || { echo "Failed to build compile" && exit 1; } 6 | cargo build --release --bin miden-run 2>/dev/null || { echo "Failed to build miden-run" && exit 1; } 7 | 8 | code="$(cat)" 9 | test_functions="$(echo "$code" | grep -E '^function test')" 10 | test_functions="$(echo "$test_functions" | sed -E 's/function ([a-zA-Z0-9_]+).*/\1/')" 11 | 12 | failures=0 13 | 14 | for f in $test_functions; do 15 | echo "$(tput bold)Running $f$(tput sgr0)" 16 | miden="$(./target/debug/compile function:"$f" <<<"$code")" 17 | ./target/release/miden-run <<<"$miden" 18 | exit_code="$?" 19 | if [[ "$f" == *"ShouldError"* ]] && [[ $exit_code -eq 0 ]]; then 20 | echo "$(tput bold)$(tput setaf 1)Test $f should have errored but didn't$(tput sgr0)" 21 | failures=$((failures + 1)) 22 | elif [[ "$f" != *"ShouldError"* ]] && [[ $exit_code -ne 0 ]]; then 23 | echo "$(tput bold)$(tput setaf 1)Test $f didn't pass$(tput sgr0)" 24 | failures=$((failures + 1)) 25 | else 26 | echo "$(tput bold)$(tput setaf 2)Test $f passed$(tput sgr0)" 27 | fi 28 | echo 29 | done 30 | 31 | if [[ $failures -eq 0 ]]; then 32 | echo "$(tput bold)$(tput setaf 2)All tests passed$(tput sgr0)" 33 | else 34 | echo "$(tput bold)$(tput setaf 1)$failures tests failed$(tput sgr0)" 35 | fi 36 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | polylang-prover = { path = "../prover" } 10 | polylang = { path = ".." } 11 | abi = { path = "../abi" } 12 | error = { path = "../error" } 13 | serde = { version = "1.0", features = ["derive", "rc"] } 14 | serde_json = { version = "1.0", features = ["arbitrary_precision"] } 15 | expect-test = "1.4.1" 16 | test-case = "3.1.0" 17 | -------------------------------------------------------------------------------- /tests/src/col_refs.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn assign_param_record_to_field_record() { 5 | let code = r#" 6 | contract User { 7 | id: string; 8 | @delegate 9 | pk: PublicKey; 10 | } 11 | 12 | contract Account { 13 | id: string; 14 | name: string; 15 | user: User; 16 | 17 | constructor (id: string, name: string, user: User) { 18 | this.id = id; 19 | this.name = name; 20 | this.user = user; 21 | } 22 | } 23 | "#; 24 | 25 | let (abi, output) = run( 26 | code, 27 | "Account", 28 | "constructor", 29 | serde_json::json!({ 30 | "id": "", 31 | "name": "", 32 | "user": { 33 | "id": "", 34 | "pk": fixtures::pk1(), 35 | }, 36 | }), 37 | vec![ 38 | serde_json::json!("john1"), 39 | serde_json::json!("John"), 40 | serde_json::json!({ 41 | "id": "user1", 42 | }), 43 | ], 44 | None, 45 | HashMap::new(), 46 | ) 47 | .unwrap(); 48 | 49 | assert_eq!( 50 | output.this(&abi).unwrap(), 51 | abi::Value::StructValue(vec![ 52 | ("id".to_owned(), abi::Value::String("john1".to_owned())), 53 | ("name".to_owned(), abi::Value::String("John".to_owned())), 54 | ( 55 | "user".to_owned(), 56 | abi::Value::ContractReference("user1".bytes().collect()), 57 | ), 58 | ]), 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /tests/src/push.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | fn run_push( 4 | arr: serde_json::Value, 5 | element: serde_json::Value, 6 | ) -> Result<(abi::Value, abi::Value), error::Error> { 7 | let code = r#" 8 | contract Account { 9 | id: string; 10 | arr: number[]; 11 | result: number; 12 | 13 | push(element: number) { 14 | this.result = this.arr.push(element); 15 | } 16 | } 17 | "#; 18 | 19 | let (abi, output) = run( 20 | code, 21 | "Account", 22 | "push", 23 | serde_json::json!({ 24 | "id": "test", 25 | "arr": arr.clone(), 26 | "result": 0, 27 | }), 28 | vec![element], 29 | None, 30 | HashMap::new(), 31 | )?; 32 | 33 | let this = output.this(&abi)?; 34 | match this { 35 | abi::Value::StructValue(fields) => { 36 | let pushed_arr = fields.iter().find(|(k, _)| k == "arr").unwrap().1.clone(); 37 | let result = fields 38 | .iter() 39 | .find(|(k, _)| k == "result") 40 | .unwrap() 41 | .1 42 | .clone(); 43 | Ok((pushed_arr, result)) 44 | } 45 | _ => panic!("unexpected value"), 46 | } 47 | } 48 | 49 | fn run_push17( 50 | arr: serde_json::Value, 51 | elements: [serde_json::Value; 17], 52 | ) -> Result<(abi::Value, abi::Value), error::Error> { 53 | let code = r#" 54 | contract Account { 55 | id: string; 56 | arr: number[]; 57 | result: number; 58 | 59 | push17(e1: number, e2: number, e3: number, e4: number, e5: number, e6: number, e7: number, e8: number, e9: number, e10: number, e11: number, e12: number, e13: number, e14: number, e15: number, e16: number, e17: number) { 60 | this.arr.push(e1); 61 | this.arr.push(e2); 62 | this.arr.push(e3); 63 | this.arr.push(e4); 64 | this.arr.push(e5); 65 | this.arr.push(e6); 66 | this.arr.push(e7); 67 | this.arr.push(e8); 68 | this.arr.push(e9); 69 | this.arr.push(e10); 70 | this.arr.push(e11); 71 | this.arr.push(e12); 72 | this.arr.push(e13); 73 | this.arr.push(e14); 74 | this.arr.push(e15); 75 | this.arr.push(e16); 76 | this.result = this.arr.push(e17); 77 | } 78 | } 79 | "#; 80 | 81 | let (abi, output) = run( 82 | code, 83 | "Account", 84 | "push17", 85 | serde_json::json!({ 86 | "id": "test", 87 | "arr": arr.clone(), 88 | "result": 0, 89 | }), 90 | elements.to_vec(), 91 | None, 92 | HashMap::new(), 93 | )?; 94 | 95 | let this = output.this(&abi)?; 96 | match this { 97 | abi::Value::StructValue(fields) => { 98 | let pushed_arr = fields.iter().find(|(k, _)| k == "arr").unwrap().1.clone(); 99 | let result = fields 100 | .iter() 101 | .find(|(k, _)| k == "result") 102 | .unwrap() 103 | .1 104 | .clone(); 105 | Ok((pushed_arr, result)) 106 | } 107 | _ => panic!("unexpected value"), 108 | } 109 | } 110 | 111 | #[test] 112 | fn test_push() { 113 | // [1, 2, 3].push(4) = [1, 2, 3, 4] 114 | let (original, result) = run_push(serde_json::json!([1, 2, 3]), serde_json::json!(4)).unwrap(); 115 | assert_eq!( 116 | original, 117 | abi::Value::Array(vec![ 118 | abi::Value::Float32(1.), 119 | abi::Value::Float32(2.), 120 | abi::Value::Float32(3.), 121 | abi::Value::Float32(4.), 122 | ]) 123 | ); 124 | assert_eq!(result, abi::Value::Float32(4.)); 125 | 126 | // [].push(1) = [1] 127 | let (original, result) = run_push(serde_json::json!([]), serde_json::json!(1)).unwrap(); 128 | assert_eq!(original, abi::Value::Array(vec![abi::Value::Float32(1.)])); 129 | assert_eq!(result, abi::Value::Float32(1.)); 130 | } 131 | 132 | #[test] 133 | fn test_push17() { 134 | // An array with 0 length is allocated with capacity 16. 135 | // We test that pushing 17 elements will cause the array to be reallocated. 136 | 137 | // [].push(1, 2, 3, ..., 17) = [1, 2, 3, ..., 17] 138 | let elements = [ 139 | serde_json::json!(1), 140 | serde_json::json!(2), 141 | serde_json::json!(3), 142 | serde_json::json!(4), 143 | serde_json::json!(5), 144 | serde_json::json!(6), 145 | serde_json::json!(7), 146 | serde_json::json!(8), 147 | serde_json::json!(9), 148 | serde_json::json!(10), 149 | serde_json::json!(11), 150 | serde_json::json!(12), 151 | serde_json::json!(13), 152 | serde_json::json!(14), 153 | serde_json::json!(15), 154 | serde_json::json!(16), 155 | serde_json::json!(17), 156 | ]; 157 | let (original, result) = run_push17(serde_json::json!([]), elements).unwrap(); 158 | assert_eq!( 159 | original, 160 | abi::Value::Array(vec![ 161 | abi::Value::Float32(1.), 162 | abi::Value::Float32(2.), 163 | abi::Value::Float32(3.), 164 | abi::Value::Float32(4.), 165 | abi::Value::Float32(5.), 166 | abi::Value::Float32(6.), 167 | abi::Value::Float32(7.), 168 | abi::Value::Float32(8.), 169 | abi::Value::Float32(9.), 170 | abi::Value::Float32(10.), 171 | abi::Value::Float32(11.), 172 | abi::Value::Float32(12.), 173 | abi::Value::Float32(13.), 174 | abi::Value::Float32(14.), 175 | abi::Value::Float32(15.), 176 | abi::Value::Float32(16.), 177 | abi::Value::Float32(17.), 178 | ]) 179 | ); 180 | assert_eq!(result, abi::Value::Float32(17.)); 181 | } 182 | -------------------------------------------------------------------------------- /tests/src/slice.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use test_case::test_case; 3 | 4 | fn run_slice( 5 | arr: serde_json::Value, 6 | start: Option, 7 | end: Option, 8 | ) -> Result { 9 | let code = r#" 10 | contract Account { 11 | id: string; 12 | arr: number[]; 13 | sliced: number[]; 14 | 15 | slice2(start: u32, end: u32) { 16 | this.sliced = this.arr.slice(start, end); 17 | } 18 | 19 | slice1(start: u32) { 20 | this.sliced = this.arr.slice(start); 21 | } 22 | 23 | slice0() { 24 | this.sliced = this.arr.slice(); 25 | } 26 | } 27 | "#; 28 | 29 | let (function_name, args) = match (start, end) { 30 | (Some(s), Some(e)) => ("slice2", vec![serde_json::json!(s), serde_json::json!(e)]), 31 | (Some(s), None) => ("slice1", vec![serde_json::json!(s)]), 32 | (None, None) => ("slice0", vec![]), 33 | _ => panic!("Unsupported argument combination"), 34 | }; 35 | 36 | let (abi, output) = run( 37 | code, 38 | "Account", 39 | function_name, 40 | serde_json::json!({ 41 | "id": "test", 42 | "arr": arr.clone(), 43 | "sliced": [], 44 | }), 45 | args, 46 | None, 47 | HashMap::new(), 48 | )?; 49 | 50 | let this = output.this(&abi)?; 51 | match this { 52 | abi::Value::StructValue(fields) => { 53 | let original_arr = fields.iter().find(|(k, _)| k == "arr").unwrap().1.clone(); 54 | let sliced = fields 55 | .iter() 56 | .find(|(k, _)| k == "sliced") 57 | .unwrap() 58 | .1 59 | .clone(); 60 | 61 | // Asserting the original array here 62 | assert_eq!( 63 | original_arr, 64 | abi::Value::Array( 65 | arr.as_array() 66 | .unwrap() 67 | .iter() 68 | .map(|v| abi::Value::Float32(v.as_f64().unwrap() as f32)) 69 | .collect() 70 | ) 71 | ); 72 | 73 | Ok(sliced) 74 | } 75 | _ => panic!("unexpected value"), 76 | } 77 | } 78 | 79 | #[test_case( 80 | serde_json::json!([1, 2, 3, 4, 5]), 81 | Some(1), 82 | Some(3), 83 | &[2., 3.] 84 | ; "slice with both args" 85 | )] 86 | #[test_case( 87 | serde_json::json!([1, 2, 3, 4, 5]), 88 | Some(2), 89 | None, 90 | &[3., 4., 5.] 91 | ; "slice with only start" 92 | )] 93 | #[test_case( 94 | serde_json::json!([1, 2, 3, 4, 5]), 95 | None, 96 | None, 97 | &[1., 2., 3., 4., 5.] 98 | ; "slice with no args" 99 | )] 100 | fn test_slice(arr: serde_json::Value, start: Option, end: Option, expected: &[f32]) { 101 | let sliced = run_slice(arr, start, end).unwrap(); 102 | assert_eq!( 103 | sliced, 104 | abi::Value::Array( 105 | expected 106 | .iter() 107 | .map(|n| abi::Value::Float32(*n)) 108 | .collect::>() 109 | ) 110 | ); 111 | } 112 | -------------------------------------------------------------------------------- /tests/src/splice.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use test_case::test_case; 3 | 4 | fn run_splice( 5 | arr: serde_json::Value, 6 | start: u32, 7 | delete_count: u32, 8 | ) -> Result<(abi::Value, abi::Value), error::Error> { 9 | let code = r#" 10 | contract Account { 11 | id: string; 12 | arr: number[]; 13 | ret: number[]; 14 | 15 | splice(start: u32, deleteCount: u32) { 16 | this.ret = this.arr.splice(start, deleteCount); 17 | } 18 | } 19 | "#; 20 | 21 | let (abi, output) = run( 22 | code, 23 | "Account", 24 | "splice", 25 | serde_json::json!({ 26 | "id": "test", 27 | "arr": arr, 28 | "ret": [], 29 | }), 30 | vec![serde_json::json!(start), serde_json::json!(delete_count)], 31 | None, 32 | HashMap::new(), 33 | )?; 34 | 35 | let this = output.this(&abi)?; 36 | let (arr, ret) = match this { 37 | abi::Value::StructValue(fields) => { 38 | let arr = fields.iter().find(|(k, _)| k == "arr").unwrap().1.clone(); 39 | let ret = fields.iter().find(|(k, _)| k == "ret").unwrap().1.clone(); 40 | (arr, ret) 41 | } 42 | _ => panic!("unexpected value"), 43 | }; 44 | 45 | Ok((arr, ret)) 46 | } 47 | 48 | #[test_case( 49 | serde_json::json!([1, 2, 3, 4, 5]), 50 | 0, 51 | 2, 52 | &[3., 4., 5.], 53 | &[1., 2.] 54 | ; "delete from start" 55 | )] 56 | #[test_case( 57 | serde_json::json!([1, 2, 3, 4, 5]), 58 | 1, 59 | 2, 60 | &[1., 4., 5.], 61 | &[2., 3.] 62 | ; "delete from middle" 63 | )] 64 | #[test_case( 65 | serde_json::json!([1, 2, 3, 4, 5]), 66 | 0, 67 | 0, 68 | &[1., 2., 3., 4., 5.], 69 | &[] 70 | ; "no delete" 71 | )] 72 | fn test_splice( 73 | arr: serde_json::Value, 74 | start: u32, 75 | delete_count: u32, 76 | expected_new_array: &[f32], 77 | expected_returned: &[f32], 78 | ) { 79 | let (arr, ret) = run_splice(arr, start, delete_count).unwrap(); 80 | assert_eq!( 81 | arr, 82 | abi::Value::Array( 83 | expected_new_array 84 | .iter() 85 | .map(|n| abi::Value::Float32(*n)) 86 | .collect::>() 87 | ) 88 | ); 89 | assert_eq!( 90 | ret, 91 | abi::Value::Array( 92 | expected_returned 93 | .iter() 94 | .map(|n| abi::Value::Float32(*n)) 95 | .collect::>() 96 | ) 97 | ); 98 | } 99 | 100 | #[test] 101 | fn test_splice_start_out_of_bounds() { 102 | assert!(run_splice(serde_json::json!([1, 2, 3, 4, 5]), 6, 0).is_err()); 103 | } 104 | -------------------------------------------------------------------------------- /tests/src/string.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | fn run_fn(f: &str, result: &str, s1: &str, s2: &str) -> Result { 4 | let code = r#" 5 | contract Account { 6 | result_bool: boolean; 7 | result_i32: i32; 8 | 9 | startsWith(x: string, y: string) { 10 | this.result_bool = x.startsWith(y); 11 | } 12 | 13 | includes(x: string, y: string) { 14 | this.result_bool = x.includes(y); 15 | } 16 | 17 | indexOf(x: string, y: string) { 18 | this.result_i32 = x.indexOf(y); 19 | } 20 | } 21 | "#; 22 | 23 | let (abi, output) = run( 24 | code, 25 | "Account", 26 | f, 27 | serde_json::json!({ 28 | "result_bool": false, 29 | "result_i32": 123, 30 | }), 31 | vec![ 32 | serde_json::Value::String(s1.into()), 33 | serde_json::Value::String(s2.into()), 34 | ], 35 | None, 36 | HashMap::new(), 37 | )?; 38 | 39 | let this = output.this(&abi)?; 40 | match this { 41 | abi::Value::StructValue(fields) => { 42 | let result = fields.iter().find(|(k, _)| k == result).unwrap().1.clone(); 43 | Ok(result) 44 | } 45 | _ => panic!("unexpected value"), 46 | } 47 | } 48 | 49 | fn run_starts_with(s1: &str, s2: &str) -> Result { 50 | run_fn("startsWith", "result_bool", s1, s2) 51 | } 52 | 53 | fn run_includes(s1: &str, s2: &str) -> Result { 54 | run_fn("includes", "result_bool", s1, s2) 55 | } 56 | 57 | fn run_index_of(s1: &str, s2: &str) -> Result { 58 | run_fn("indexOf", "result_i32", s1, s2) 59 | } 60 | 61 | #[test_case::test_case("qwe", "qwe", true; "exact match")] 62 | #[test_case::test_case("qwe", "ewq", false; "same size mismatch")] 63 | #[test_case::test_case("qweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqwe", "qweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqweqwe", true; "exact long match")] 64 | #[test_case::test_case("𝔍К𝓛𝓜ƝȎ𝚸𝑄Ṛ𝓢ṮṺƲᏔꓫ𝚈𝚭𝜶Ꮟ", "𝔍К𝓛𝓜ƝȎ𝚸𝑄Ṛ𝓢ṮṺƲᏔꓫ𝚈𝚭𝜶Ꮟ", true; "unicode")] 65 | #[test_case::test_case("qwer", "qwe", true; "substring match")] 66 | #[test_case::test_case("qwe", "qwef", false; "second larger")] 67 | #[test_case::test_case("qwert", "wer", false; "substring but not start")] 68 | #[test_case::test_case("", "", true; "empty strings")] 69 | fn test_starts_with(s1: &str, s2: &str, expected: bool) { 70 | let result = run_starts_with(s1, s2).unwrap(); 71 | assert_eq!(result, abi::Value::Boolean(expected)); 72 | } 73 | 74 | #[test_case::test_case("qwe", "qwe", true; "exact match")] 75 | #[test_case::test_case("qwe", "ewq", false; "same size mismatch")] 76 | #[test_case::test_case("qwerty", "qwert", true; "substring start")] 77 | #[test_case::test_case("asdqwe", "dqwe", true; "substring end")] 78 | #[test_case::test_case("asqwerty", "we", true; "substring middle")] 79 | #[test_case::test_case("𝔍К𝓛𝓜ƝȎ𝚸𝑄Ṛ𝓢ṮṺƲᏔꓫ𝚈𝚭𝜶Ꮟ", "𝑄Ṛ𝓢ṮṺƲᏔꓫ𝚈", true; "unicode")] 80 | #[test_case::test_case("qwe", "qwef", false; "second larger")] 81 | #[test_case::test_case("", "", true; "empty strings")] 82 | fn test_includes(s1: &str, s2: &str, expected: bool) { 83 | let result = run_includes(s1, s2).unwrap(); 84 | assert_eq!(result, abi::Value::Boolean(expected)); 85 | } 86 | 87 | #[test_case::test_case("qwe", "qwe", 0; "exact match")] 88 | #[test_case::test_case("qwe", "ewq", -1; "same size mismatch")] 89 | #[test_case::test_case("qwerty", "qwert", 0; "substring start")] 90 | #[test_case::test_case("asdqwe", "dqwe", 2; "substring end")] 91 | #[test_case::test_case("asqwerty", "we", 3; "substring middle")] 92 | // TODO: now it returns byte index, not char codepoint. 93 | // #[test_case::test_case("𝔍К𝓛𝓜ƝȎ𝚸𝑄Ṛ𝓢ṮṺƲᏔꓫ𝚈𝚭𝜶Ꮟ", "𝑄Ṛ𝓢ṮṺƲᏔꓫ𝚈", 7; "unicode")] 94 | #[test_case::test_case("qwe", "qwef", -1; "second larger")] 95 | #[test_case::test_case("", "", 0; "empty strings")] 96 | fn test_index_of(s1: &str, s2: &str, expected: i32) { 97 | let result = run_index_of(s1, s2).unwrap(); 98 | assert_eq!(result, abi::Value::Int32(expected)); 99 | } 100 | -------------------------------------------------------------------------------- /tests/src/unshift.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use test_case::test_case; 3 | 4 | fn run_unshift( 5 | arr: serde_json::Value, 6 | elems: Vec, 7 | ) -> Result<(abi::Value, abi::Value), error::Error> { 8 | let code = r#" 9 | contract Account { 10 | id: string; 11 | arr: number[]; 12 | len: u32; 13 | 14 | unshift1(elem1: number) { 15 | this.len = this.arr.unshift(elem1); 16 | } 17 | 18 | unshift2(elem1: number, elem2: number) { 19 | this.len = this.arr.unshift(elem1, elem2); 20 | } 21 | } 22 | "#; 23 | 24 | let (abi, output) = run( 25 | code, 26 | "Account", 27 | match elems.len() { 28 | 1 => "unshift1", 29 | 2 => "unshift2", 30 | _ => panic!("unexpected number of elements"), 31 | }, 32 | serde_json::json!({ 33 | "id": "test", 34 | "arr": arr, 35 | "len": 0, 36 | }), 37 | elems, 38 | None, 39 | HashMap::new(), 40 | )?; 41 | 42 | let this = output.this(&abi)?; 43 | let (arr, len) = match this { 44 | abi::Value::StructValue(fields) => { 45 | let arr = fields.iter().find(|(k, _)| k == "arr").unwrap().1.clone(); 46 | let len = fields.iter().find(|(k, _)| k == "len").unwrap().1.clone(); 47 | (arr, len) 48 | } 49 | _ => panic!("unexpected value"), 50 | }; 51 | 52 | Ok((arr, len)) 53 | } 54 | 55 | #[test_case( 56 | serde_json::json!([2, 3, 4]), 57 | vec![serde_json::json!(1)], 58 | &[1., 2., 3., 4.], 59 | 4 60 | ; "unshift single element" 61 | )] 62 | #[test_case( 63 | serde_json::json!([3, 4, 5]), 64 | vec![serde_json::json!(1), serde_json::json!(2)], 65 | &[1., 2., 3., 4., 5.], 66 | 5 67 | ; "unshift two elements" 68 | )] 69 | #[test_case( 70 | serde_json::json!([]), 71 | vec![serde_json::json!(1)], 72 | &[1.], 73 | 1 74 | ; "unshift empty array" 75 | )] 76 | fn test_unshift( 77 | arr: serde_json::Value, 78 | elems: Vec, 79 | expected_arr: &[f32], 80 | expected_len: u32, 81 | ) { 82 | let (arr, len) = run_unshift(arr, elems).unwrap(); 83 | assert_eq!( 84 | arr, 85 | abi::Value::Array( 86 | expected_arr 87 | .iter() 88 | .map(|n| abi::Value::Float32(*n)) 89 | .collect::>() 90 | ) 91 | ); 92 | assert_eq!(len, abi::Value::UInt32(expected_len)); 93 | } 94 | -------------------------------------------------------------------------------- /wasm-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm-api" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | crate-type = ["cdylib", "rlib"] 10 | 11 | [dependencies] 12 | abi = { path = "../abi" } 13 | polylang-prover = { path = "../prover" } 14 | polylang = { path = "../" } 15 | miden-vm = { git = "https://github.com/0xPolygonMiden/miden-vm", tag = "v0.7.0", default-features = false } 16 | error = { path = "../error" } 17 | serde = { version = "1.0", features = ["derive", "rc"] } 18 | serde_json = { version = "1.0", features = ["arbitrary_precision"] } 19 | wasm-bindgen = "0.2" 20 | serde-wasm-bindgen = "0.6" 21 | base64 = "0.21.4" 22 | -------------------------------------------------------------------------------- /wasm-api/README.md: -------------------------------------------------------------------------------- 1 | WASM API for compiling and running Polylang programs. 2 | 3 | ```sh 4 | wasm-pack build --target nodejs 5 | node example.js 6 | ``` 7 | -------------------------------------------------------------------------------- /wasm-api/example.js: -------------------------------------------------------------------------------- 1 | const pkg = require("./pkg"); 2 | 3 | pkg.init(); 4 | 5 | function justMain() { 6 | let program = pkg.compile( 7 | "function main(x: string): string { log(x); return 'x: ' + x; }", 8 | null, 9 | "main" 10 | ); 11 | let output = program.run( 12 | JSON.stringify(null), 13 | JSON.stringify(["hello world"]), 14 | // true == generate a proof 15 | true 16 | ); 17 | 18 | return output; 19 | } 20 | 21 | function withContracts() { 22 | let program = pkg.compile( 23 | // If the log was absent, we wouldn't get `id` in the output, 24 | // because the compiler optimizes it away for performance 25 | "contract Account { id: string; function main() { log(this.id); } }", 26 | "Account", 27 | "main" 28 | ); 29 | let output = program.run( 30 | JSON.stringify({ id: "test" }), 31 | JSON.stringify([]), 32 | true 33 | ); 34 | 35 | return output; 36 | } 37 | 38 | function withArrays() { 39 | let program = pkg.compile( 40 | `contract ReverseArray { 41 | elements: number[]; 42 | 43 | constructor (elements: number[]) { 44 | this.elements = elements; 45 | } 46 | 47 | function reverse(): number[] { 48 | let reversed: u32[] = []; 49 | let i: u32 = 0; 50 | let one: u32 = 1; 51 | let len: u32 = this.elements.length; 52 | 53 | while (i < len) { 54 | let idx: u32 = len - i - one; 55 | reversed.push(this.elements[idx]); 56 | i = i + one; 57 | } 58 | 59 | return reversed; 60 | } 61 | }`, 62 | "ReverseArray", 63 | "reverse" 64 | ); 65 | let output = program.run( 66 | JSON.stringify({ elements: [1, 2, 3, 4, 5] }), 67 | JSON.stringify([]), 68 | true 69 | ); 70 | 71 | return output; 72 | } 73 | 74 | function withCountryCity() { 75 | let program = pkg.compile(` 76 | contract City { 77 | id: string; 78 | name: string; 79 | country: Country; 80 | 81 | constructor(id: string, name: string, country: Country) { 82 | this.id = id; 83 | this.name = name; 84 | this.country = country; 85 | } 86 | } 87 | 88 | contract Country { 89 | id: string; 90 | name: string; 91 | 92 | constructor (id: string, name: string) { 93 | this.id = id; 94 | this.name = name; 95 | } 96 | } 97 | `, 98 | "City", 99 | "constructor") 100 | 101 | let output = program.run( 102 | JSON.stringify({ id: "", name: "", country: { id: "", name: "" } }), 103 | JSON.stringify(["boston", "BOSTON", { id: "usa", name: "USA" }]), 104 | true 105 | ); 106 | 107 | return output; 108 | } 109 | 110 | function fibonacci() { 111 | let program = pkg.compile(` 112 | function main(p: u32, a: u32, b: u32) { 113 | for (let i: u32 = 0; i < p; i++) { 114 | let c = a.wrappingAdd(b); 115 | a = b; 116 | b = c; 117 | } 118 | }`, 119 | null, 120 | "main" 121 | ) 122 | 123 | let output = program.run( 124 | JSON.stringify(null), 125 | JSON.stringify([30, 1, 1]), 126 | true 127 | ); 128 | 129 | return output; 130 | } 131 | 132 | function report(output, hasThis) { 133 | return { 134 | proof: output.proof(), 135 | proofLength: output.proof().length, 136 | cycleCount: output.cycle_count(), 137 | this: hasThis ? output.this() : null, 138 | result: output.result(), 139 | resultHash: output.result_hash(), 140 | logs: output.logs(), 141 | hashes: output.hashes(), 142 | // selfDestructed: output.self_destructed(), 143 | readAuth: output.read_auth(), 144 | }; 145 | } 146 | 147 | function verifyProof(output) { 148 | const proof = output.proof(); 149 | const program_info = output.program_info(); 150 | const stack_inputs = output.stack_inputs(); 151 | const output_stack = output.output_stack(); 152 | const overflow_addrs = output.overflow_addrs(); 153 | 154 | console.log("Proof is valid?", pkg.verify(proof, program_info, stack_inputs, output_stack, overflow_addrs)); 155 | } 156 | 157 | const mainOutput = justMain(); 158 | console.log(report(mainOutput, false)); 159 | verifyProof(mainOutput); 160 | 161 | const contractOutput = withContracts(); 162 | console.log(report(contractOutput, true)); 163 | verifyProof(contractOutput); 164 | 165 | const arraysOutput = withArrays(); 166 | console.log(report(arraysOutput, true)); 167 | verifyProof(arraysOutput); 168 | 169 | const countryCityOutput = withCountryCity(); 170 | console.log(report(countryCityOutput, true)); 171 | verifyProof(countryCityOutput); 172 | 173 | const fibonacciOutput = fibonacci(); 174 | console.log(report(fibonacciOutput, false)); 175 | verifyProof(fibonacciOutput); 176 | -------------------------------------------------------------------------------- /wasm-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polybase/polylang-prover", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "./pkg/index.js", 6 | "types": "./pkg/index.d.ts", 7 | "license": "MIT", 8 | "files": [ 9 | "pkg/*" 10 | ], 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\"", 13 | "clean": "rimraf node/* && rimraf web/*", 14 | "build": "yarn clean && webpack build", 15 | "prepare": "yarn build", 16 | "fix": "yarn eslint \"./src/**/*.{ts,tsx}\" webpack.config.js --fix" 17 | }, 18 | "devDependencies": { 19 | "@wasm-tool/wasm-pack-plugin": "^1.6.0", 20 | "rimraf": "^3.0.2", 21 | "webpack": "^5.74.0", 22 | "webpack-cli": "^4.10.0", 23 | "webpack-node-externals": "^3.0.0" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/polybase/polylang.git" 28 | }, 29 | "engines": { 30 | "node": ">=16.0.0" 31 | } 32 | } -------------------------------------------------------------------------------- /wasm-api/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin') 4 | 5 | const inlineWASM = (pkgDir) => ({ 6 | apply: (compiler) => { 7 | compiler.hooks.beforeCompile.tap('InlineWASM', () => { 8 | const wasm = fs.readFileSync( 9 | path.resolve(__dirname, pkgDir + '/index_bg.wasm'), 10 | ) 11 | 12 | const js = ` 13 | import * as index_bg from "./index_bg.js" 14 | 15 | const base64 = "${Buffer.from(wasm).toString('base64')}" 16 | 17 | function toUint8Array (s) { 18 | return new Uint8Array(atob(s).split('').map(c => c.charCodeAt(0))) 19 | } 20 | 21 | const wasm = toUint8Array(base64) 22 | 23 | const { instance } = await WebAssembly.instantiate(wasm, { 24 | "./index_bg.js": index_bg, 25 | }) 26 | 27 | export default instance.exports 28 | ` 29 | 30 | fs.writeFileSync(path.resolve(__dirname, pkgDir + '/index_bg.wasm.js'), js) 31 | 32 | const index = fs.readFileSync( 33 | path.resolve(__dirname, pkgDir + '/index_bg.js'), 34 | ) 35 | 36 | fs.writeFileSync( 37 | path.resolve(__dirname, pkgDir + '/index_bg.js'), 38 | index 39 | .toString() 40 | .replace( 41 | 'let wasm;', 42 | 'import wasm from \'./index_bg.js\'', 43 | ), 44 | ) 45 | 46 | fs.unlinkSync(path.resolve(__dirname, pkgDir + '/index_bg.wasm')) 47 | }) 48 | }, 49 | }) 50 | 51 | module.exports = { 52 | // ... 53 | 54 | plugins: [ 55 | new WasmPackPlugin({ 56 | crateDirectory: path.resolve(__dirname, '.'), 57 | 58 | // Check https://rustwasm.github.io/wasm-pack/book/commands/build.html for 59 | // the available set of arguments. 60 | // 61 | // Optional space delimited arguments to appear before the wasm-pack 62 | // command. Default arguments are `--verbose`. 63 | args: '--log-level warn', 64 | // Default arguments are `--typescript --target browser --mode normal`. 65 | 66 | // Optional array of absolute paths to directories, changes to which 67 | // will trigger the build. 68 | // watchDirectories: [ 69 | // path.resolve(__dirname, "another-crate/src") 70 | // ], 71 | 72 | // The same as the `--out-dir` option for `wasm-pack` 73 | // outDir: "pkg", 74 | 75 | // The same as the `--out-name` option for `wasm-pack` 76 | // outName: "index", 77 | 78 | // If defined, `forceWatch` will force activate/deactivate watch mode for 79 | // `.rs` files. 80 | // 81 | // The default (not set) aligns watch mode for `.rs` files to Webpack's 82 | // watch mode. 83 | // forceWatch: true, 84 | 85 | // If defined, `forceMode` will force the compilation mode for `wasm-pack` 86 | // 87 | // Possible values are `development` and `production`. 88 | // 89 | // the mode `development` makes `wasm-pack` build in `debug` mode. 90 | // the mode `production` makes `wasm-pack` build in `release` mode. 91 | // forceMode: "development", 92 | 93 | // Controls plugin output verbosity, either 'info' or 'error'. 94 | // Defaults to 'info'. 95 | // pluginLogLevel: 'info' 96 | }), 97 | inlineWASM('pkg'), 98 | ], 99 | 100 | // ... 101 | } --------------------------------------------------------------------------------