diff --git a/.eslintrc.json b/.eslintrc.json index 43f98d9919..01f436aaa8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,6 +12,7 @@ "rules": { "no-unused-vars": "warn", "no-constant-condition": "off", - "no-empty": "off" + "no-empty": "off", + "no-unused-labels": "off" } } diff --git a/.github/actions/live-tests-shared/action.yml b/.github/actions/live-tests-shared/action.yml new file mode 100644 index 0000000000..8bd6d166af --- /dev/null +++ b/.github/actions/live-tests-shared/action.yml @@ -0,0 +1,39 @@ +name: 'Shared steps for live testing jobs' +description: 'Shared steps for live testing jobs' +inputs: + mina-branch-name: + description: 'Mina branch name in use by service container' + required: true +runs: + using: 'composite' + steps: + - name: Wait for Mina network readiness + uses: o1-labs/wait-for-mina-network-action@v1 + with: + mina-graphql-port: 8080 + max-attempts: 60 + polling-interval-ms: 10000 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Build o1js and execute tests + env: + USE_CUSTOM_LOCAL_NETWORK: 'true' + run: | + git submodule update --init --recursive + npm ci + npm run build + touch profiling.md + bash run-ci-live-tests.sh + cat profiling.md >> $GITHUB_STEP_SUMMARY + shell: bash + - name: Upload Mina logs + uses: actions/upload-artifact@v3 + continue-on-error: true + if: always() + with: + if-no-files-found: ignore + name: mina-logs-${{ inputs.mina-branch-name }} + path: /tmp/*.log + retention-days: 5 diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml new file mode 100644 index 0000000000..db25ed78c0 --- /dev/null +++ b/.github/workflows/benchmarks.yml @@ -0,0 +1,43 @@ +name: Benchmark o1js +on: + push: + branches: + - main + - berkeley + - develop + pull_request: + workflow_dispatch: {} + +jobs: + benchmarks: + timeout-minutes: 30 + strategy: + fail-fast: true + matrix: + node: [20] + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Node.JS ${{ matrix.node }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - name: Build o1js and execute benchmarks on ${{ matrix.os }} and Node.JS ${{ matrix.node }} + env: + GIT_BRANCH: ${{ github.head_ref || github.ref_name }} + INFLUXDB_URL: ${{ secrets.INFLUXDB_URL }} + INFLUXDB_ORG: ${{ secrets.INFLUXDB_ORG }} + INFLUXDB_BUCKET: ${{ secrets.INFLUXDB_BUCKET }} + INFLUXDB_TOKEN: ${{ secrets.INFLUXDB_TOKEN }} + METRICS_SOURCE_ENVIRONMENT: 'o1js GitHub Actions' + METRICS_BASE_BRANCH_FOR_COMPARISON: 'main' + run: | + git submodule update --init --recursive + npm ci + npm run build + echo 'Running o1js benchmarks.' + node --enable-source-maps --stack-trace-limit=1000 src/build/run.js benchmark/runners/with-cloud-history.ts --bundle >>benchmarks.log 2>&1 + cat benchmarks.log >> $GITHUB_STEP_SUMMARY + shell: bash diff --git a/.github/workflows/build-action.yml b/.github/workflows/build-action.yml index f042184e0f..43cea697ff 100644 --- a/.github/workflows/build-action.yml +++ b/.github/workflows/build-action.yml @@ -1,4 +1,4 @@ -name: Build SnarkyJS +name: Build o1js on: push: branches: @@ -6,10 +6,6 @@ on: - berkeley - develop pull_request: - branches: - - main - - berkeley - - develop workflow_dispatch: {} jobs: @@ -30,13 +26,13 @@ jobs: 'CommonJS test', ] steps: - - name: Checkout Repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: node-version: '18' - - name: Build SnarkyJS and Execute Tests + - name: Build o1js and execute tests env: TEST_TYPE: ${{ matrix.test_type }} continue-on-error: false @@ -52,28 +48,30 @@ jobs: timeout-minutes: 90 runs-on: ubuntu-latest steps: - - name: Checkout Repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: node-version: '18' - - name: Install Node Dependencies + - name: Install Node dependencies run: | git submodule update --init --recursive npm ci - - name: Install Playwright Browsers + - name: Install Playwright browsers run: npm run e2e:install - - name: Build SnarkyJS and Prepare the Web Server + - name: Build o1js and prepare the web server run: | npm run build:web npm run e2e:prepare-server - - name: Execute E2E Tests + - name: Execute E2E tests run: npm run test:e2e - - name: Upload E2E Test Artifacts - uses: actions/upload-artifact@v2 + - name: Upload E2E test artifacts + uses: actions/upload-artifact@v3 + continue-on-error: true if: always() with: + if-no-files-found: ignore name: e2e-tests-report path: tests/report/ retention-days: 30 @@ -83,13 +81,13 @@ jobs: runs-on: ubuntu-latest needs: [Build-And-Test-Server, Build-And-Test-Web] steps: - - name: Checkout Repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: node-version: '18' - - name: Build SnarkyJS + - name: Build o1js run: | git submodule update --init --recursive npm ci @@ -107,10 +105,10 @@ jobs: runs-on: ubuntu-latest needs: [Build-And-Test-Server, Build-And-Test-Web] steps: - - name: Checkout Repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: node-version: '18' - name: Build mina-signer diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 343522198a..4c0d15f70d 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -1,4 +1,4 @@ -name: Build SnarkyJS +name: Build o1js on: push: branches: @@ -8,21 +8,21 @@ jobs: Build-Doc: runs-on: ubuntu-latest steps: - - name: Checkout Repository + - name: Checkout repository uses: actions/checkout@v2 - - name: Setup node + - name: Setup Node uses: actions/setup-node@v2 with: node-version: '16' - - name: run typedoc + - name: Run typedoc run: | git submodule update --init --recursive npm ci npx typedoc --tsconfig tsconfig.node.json src/index.ts - - name: deploy + - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/live-tests.yml b/.github/workflows/live-tests.yml new file mode 100644 index 0000000000..958c9bacfd --- /dev/null +++ b/.github/workflows/live-tests.yml @@ -0,0 +1,92 @@ +name: Test o1js against the real network +on: + push: + branches: + - main + - berkeley + - develop + pull_request: + branches: + - main + - berkeley + - develop + workflow_dispatch: {} + +jobs: + main-branch: + timeout-minutes: 45 + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.base_ref == 'main') + services: + mina-local-network: + image: o1labs/mina-local-network:o1js-main-latest-lightnet + env: + NETWORK_TYPE: 'single-node' + PROOF_LEVEL: 'none' + ports: + - 3085:3085 + - 5432:5432 + - 8080:8080 + - 8181:8181 + - 8282:8282 + volumes: + - /tmp:/root/logs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Use shared steps for live testing jobs + uses: ./.github/actions/live-tests-shared + with: + mina-branch-name: o1js-main + + berkeley-branch: + timeout-minutes: 45 + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/berkeley' || (github.event_name == 'pull_request' && github.base_ref == 'berkeley') + services: + mina-local-network: + image: o1labs/mina-local-network:berkeley-latest-lightnet + env: + NETWORK_TYPE: 'single-node' + PROOF_LEVEL: 'none' + ports: + - 3085:3085 + - 5432:5432 + - 8080:8080 + - 8181:8181 + - 8282:8282 + volumes: + - /tmp:/root/logs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Use shared steps for live testing jobs + uses: ./.github/actions/live-tests-shared + with: + mina-branch-name: berkeley + + develop-branch: + timeout-minutes: 45 + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/develop' || (github.event_name == 'pull_request' && github.base_ref == 'develop') + services: + mina-local-network: + image: o1labs/mina-local-network:develop-latest-lightnet + env: + NETWORK_TYPE: 'single-node' + PROOF_LEVEL: 'none' + ports: + - 3085:3085 + - 5432:5432 + - 8080:8080 + - 8181:8181 + - 8282:8282 + volumes: + - /tmp:/root/logs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Use shared steps for live testing jobs + uses: ./.github/actions/live-tests-shared + with: + mina-branch-name: develop diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..971fd9d5e9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,82 @@ +# Purpose: +# Automatically bumps the project's patch version bi-weekly on Tuesdays. +# +# Details: +# - Triggered at 00:00 UTC every Tuesday; runs on even weeks of the year. +# - Sets up the environment by checking out the repo and setting up Node.js. +# - Bumps patch version using `npm version patch`, then creates a new branch 'release/x.x.x'. +# - Pushes changes and creates a PR to `main` using GitHub CLI. +# - Can also be triggered manually via `workflow_dispatch`. +name: Version Bump + +on: + workflow_dispatch: # Allow to manually trigger the workflow + schedule: + - cron: '0 0 * * 2' # At 00:00 UTC every Tuesday + +jobs: + version-bump: + runs-on: ubuntu-latest + + steps: + # Since cronjob syntax doesn't support bi-weekly schedule, we need to check if it's an even week or not + - name: Check if it's an even week + run: | + WEEK_NUM=$(date +'%V') + WEEK_NUM=$((10#$WEEK_NUM)) + if [ $((WEEK_NUM % 2)) -ne 0 ]; then + echo "Odd week number ($WEEK_NUM), terminating job." + exit 1 + fi + + - name: Check out the repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Configure Git + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + - name: Bump patch version + run: | + git fetch --prune --unshallow --tags --force + NEW_VERSION=$(npm version patch) + echo "New version: $NEW_VERSION" + echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV + + - name: Install npm dependencies + run: npm install + + - name: Update CHANGELOG.md + run: | + npm run update-changelog + git add CHANGELOG.md + git commit -m "Update CHANGELOG for new version $NEW_VERSION" + + - name: Delete existing release tag + run: | + if git rev-parse $NEW_VERSION >/dev/null 2>&1; then + git tag -d $NEW_VERSION + git push origin :refs/tags/$NEW_VERSION + fi + + - name: Delete existing release branch + run: | + if git ls-remote --heads origin release/${NEW_VERSION} | grep release/${NEW_VERSION}; then + git push origin --delete release/${NEW_VERSION} + fi + + - name: Create new release branch + run: | + NEW_BRANCH="release/${NEW_VERSION}" + git checkout -b $NEW_BRANCH + git push -u origin $NEW_BRANCH + git push --tags + gh pr create --base main --head $NEW_BRANCH --title "Release $NEW_VERSION" --body "This is an automated PR to update to version $NEW_VERSION" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN}} diff --git a/.gitignore b/.gitignore index 9246171b93..78d26c7d8a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ o1labs*.tgz tests/report/ tests/test-artifacts/ profiling.md -snarkyjs-reference +o1js-reference *.tmp.js _build/ src/config/ diff --git a/.gitmodules b/.gitmodules index 76a36100bc..9e993ea759 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "src/snarkyjs-bindings"] +[submodule "src/o1js-bindings"] path = src/bindings - url = https://github.com/o1-labs/snarkyjs-bindings.git + url = https://github.com/o1-labs/o1js-bindings.git [submodule "src/mina"] path = src/mina url = https://github.com/MinaProtocol/mina.git diff --git a/.prettierignore b/.prettierignore index fe67df0c2c..c3f051e6bb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,3 @@ src/bindings/compiled/web_bindings/**/plonk_wasm* src/bindings/compiled/web_bindings/**/*.bc.js src/bindings/compiled/node_bindings/* dist/**/* -src/mina/src/lib/crypto/kimchi/js/**/*.js diff --git a/CHANGELOG.md b/CHANGELOG.md index a935f8deeb..2786bad10b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,325 +15,647 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm _Security_ in case of vulnerabilities. --> -## [Unreleased](https://github.com/o1-labs/snarkyjs/compare/b1d8d5910...HEAD) +## [Unreleased](https://github.com/o1-labs/o1js/compare/74948acac...HEAD) ### Breaking changes -- Changes to verification keys caused by updates to the proof system. This breaks all deployed contracts https://github.com/o1-labs/snarkyjs/pull/1016 +- **Async circuits**. Require all smart contract and zkprogram methods to be async https://github.com/o1-labs/o1js/pull/1477 + - This change allows you to use `await` inside your methods. Change the method signature by adding the `async` keyword. + - Don't forget to add `await` to all contract calls! `await MyContract.myMethod();` + - To declare a return value from a method, use the new `@method.returns()` decorator +- Require the callback to `Mina.transaction()` to be async https://github.com/o1-labs/o1js/pull/1468 +- Change `{SmartContract,ZkProgram}.analyzeMethods()` to be async https://github.com/o1-labs/o1js/pull/1450 + - `Provable.runAndCheck()`, `Provable.constraintSystem()` and `{SmartContract,ZkProgram}.digest()` are also async now +- **Remove deprecated APIs** + - Remove `CircuitValue`, `prop`, `arrayProp` and `matrixProp` https://github.com/o1-labs/o1js/pull/1507 + - Remove `Mina.accountCreationFee()`, `Mina.BerkeleyQANet`, all APIs which accept private keys for feepayers, `Token`, `AccountUpdate.tokenSymbol`, `SmartContract.{token, setValue, setPermissions}`, "assert" methods for preconditions, `MerkleTee.calculateRootSlow()`, `Scalar.fromBigInt()`, `UInt64.lt()` and friends, deprecated static methods on `Group`, utility methods on `Circuit` like `Circuit.if()`, `Field.isZero()`, `isReady` and `shutdown()` https://github.com/o1-labs/o1js/pull/1515 +- Remove `privateKey` from the accepted arguments of `SmartContract.deploy()` https://github.com/o1-labs/o1js/pull/1515 +- **Efficient comparisons**. Support arbitrary bit lengths for `Field` comparisons and massively reduce their constraints https://github.com/o1-labs/o1js/pull/1523 + - `Field.assertLessThan()` goes from 510 to 24 constraints, `Field.lessThan()` from 509 to 38 + - Moderately improve other comparisons: `UInt64.assertLessThan()` from 27 to 14, `UInt64.lessThan()` from 27 to 15, `UInt32` similar. + - Massively improve `Field.isEven()`, add `Field.isOdd()` + - `PrivateKey.toPublicKey()` from 358 to 119 constraints thanks to `isOdd()` + - Add `Gadgets.ForeignField.assertLessThanOrEqual()` and support two variables as input to `ForeignField.assertLessThan()` +- Remove `this.sender` which unintuitively did not prove that its value was the actual sender of the transaction https://github.com/o1-labs/o1js/pull/1464 [@julio4](https://github.com/julio4) + Replaced by more explicit APIs: + - `this.sender.getUnconstrained()` which has the old behavior of `this.sender`, and returns an unconstrained value (which means that the prover can set it to any value they want) + - `this.sender.getAndRequireSignature()` which requires a signature from the sender's public key and therefore proves that whoever created the transaction really owns the sender account +- `Reducer.reduce()` requires the maximum number of actions per method as an explicit (optional) argument https://github.com/o1-labs/o1js/pull/1450 + - The default value is 1 and should work for most existing contracts +- `new UInt64()` and `UInt64.from()` no longer unsafely accept a field element as input. https://github.com/o1-labs/o1js/pull/1438 [@julio4](https://github.com/julio4) + As a replacement, `UInt64.Unsafe.fromField()` was introduced + - This prevents you from accidentally creating a `UInt64` without proving that it fits in 64 bits + - Equivalent changes were made to `UInt32` +- Fixed vulnerability in `Field.to/fromBits()` outlined in [#1023](https://github.com/o1-labs/o1js/issues/1023) by imposing a limit of 254 bits https://github.com/o1-labs/o1js/pull/1461 +- Remove `Field.rangeCheckHelper()` which was too low-level and easy to misuse https://github.com/o1-labs/o1js/pull/1485 + - Also, rename the misleadingly named `Gadgets.isInRangeN()` to `Gadgets.isDefinitelyInRangeN()` +- Rename `Bool.Unsafe.ofField()` to `Bool.Unsafe.fromField()` https://github.com/o1-labs/o1js/pull/1485 +- Replace the namespaced type exports `Gadgets.Field3` and `Gadgets.ForeignField.Sum` with `Field3` and `ForeignFieldSum` + - Unfortunately, the namespace didn't play well with auto-imports in TypeScript -## [0.12.1](https://github.com/o1-labs/snarkyjs/compare/161b69d602...b1d8d5910) +### Added + +- `Provable.witnessAsync()` to introduce provable values from an async callback https://github.com/o1-labs/o1js/pull/1468 +- Internal benchmarking tooling to keep track of performance https://github.com/o1-labs/o1js/pull/1481 +- Add `toInput` method for `Group` instance https://github.com/o1-labs/o1js/pull/1483 + +### Changed + +- `field.assertBool()` now also returns the `Field` as a `Bool` for ergonomics https://github.com/o1-labs/o1js/pull/1523 + +## [0.17.0](https://github.com/o1-labs/o1js/compare/1ad7333e9e...74948acac) - 2024-03-06 + +### Breaking changes + +- Fixed parity between `Mina.LocalBlockchain` and `Mina.Network` to have the same behaviors https://github.com/o1-labs/o1js/pull/1422 https://github.com/o1-labs/o1js/pull/1480 + - Changed the `TransactionId` type to `Transaction`. Additionally added `PendingTransaction` and `RejectedTransaction` types to better represent the state of a transaction. + - `Transaction.safeSend()` and `PendingTransaction.safeWait()` are introduced to return a `IncludedTransaction` or `RejectedTransaction` object without throwing errors. + - `transaction.send()` throws an error if the transaction was not successful for both `Mina.LocalBlockchain` and `Mina.Network` and returns a `PendingTransaction` object if it was successful. Use `transaction.safeSend` to send a transaction that will not throw an error and either return a `PendingTransaction` or `RejectedTransaction`. + - `transaction.wait()` throws an error if the transaction was not successful for both `Mina.LocalBlockchain` and `Mina.Network` and returns a `IncludedTransaction` object if it was successful. Use `transaction.safeWait` to send a transaction that will not throw an error and either return a `IncludedTransaction` or `RejectedTransaction`. + - `transaction.hash()` is no longer a function, it is now a property that returns the hash of the transaction. + - Changed `Transaction.isSuccess` to `Transaction.status` to better represent the state of a transaction. +- Improved efficiency of computing `AccountUpdate.callData` by packing field elements into as few field elements as possible https://github.com/o1-labs/o1js/pull/1458 + - This leads to a large reduction in the number of constraints used when inputs to a zkApp method are many field elements (e.g. a long list of `Bool`s) +- Return events in the `LocalBlockchain` in reverse chronological order (latest events at the beginning) to match the behavior of the `Network` https://github.com/o1-labs/o1js/pull/1460 + +### Added + +- Support for custom network identifiers other than `mainnet` or `testnet` https://github.com/o1-labs/o1js/pull/1444 +- `PrivateKey.randomKeypair()` to generate private and public key in one command https://github.com/o1-labs/o1js/pull/1446 +- `setNumberOfWorkers()` to allow developer to override the number of workers used during compilation and proof generation/verification https://github.com/o1-labs/o1js/pull/1456 + +### Changed + +- Improve all-around performance by reverting the Apple silicon workaround (https://github.com/o1-labs/o1js/pull/683) as the root problem is now fixed upstream https://github.com/o1-labs/o1js/pull/1456 +- Improved error message when trying to use `fetchActions`/`fetchEvents` with a missing Archive Node endpoint https://github.com/o1-labs/o1js/pull/1459 + +### Deprecated + +- `SmartContract.token` is deprecated in favor of new methods on `TokenContract` https://github.com/o1-labs/o1js/pull/1446 + - `TokenContract.deriveTokenId()` to get the ID of the managed token + - `TokenContract.internal.{send, mint, burn}` to perform token operations from within the contract + +### Fixed + +- Mitigate security hazard of deploying token contracts https://github.com/o1-labs/o1js/issues/1439 +- Make `Circuit` handle types with a `.provable` property (like those used in ECDSA) https://github.com/o1-labs/o1js/pull/1471 + - To support offchain, non-Pickles proofs of ECDSA signatures + +## [0.16.1](https://github.com/o1-labs/o1js/compare/834a44002...3b5f7c7) + +### Breaking changes + +- Remove `AccountUpdate.children` and `AccountUpdate.parent` properties https://github.com/o1-labs/o1js/pull/1402 + - Also removes the optional `AccountUpdatesLayout` argument to `approve()` + - Adds `AccountUpdateTree` and `AccountUpdateForest`, new classes that represent a layout of account updates explicitly + - Both of the new types are now accepted as inputs to `approve()` + - `accountUpdate.extractTree()` to obtain the tree associated with an account update in the current transaction context. +- Remove `Experimental.Callback` API https://github.com/o1-labs/o1js/pull/1430 + +### Added + +- `MerkleList` to enable provable operations on a dynamically-sized list https://github.com/o1-labs/o1js/pull/1398 + - including `MerkleListIterator` to iterate over a merkle list +- `TokenContract`, a new base smart contract class for token contracts https://github.com/o1-labs/o1js/pull/1384 + - Usage example: `https://github.com/o1-labs/o1js/blob/main/src/lib/mina/token/token-contract.unit-test.ts` +- `TokenAccountUpdateIterator`, a primitive to iterate over all token account updates in a transaction https://github.com/o1-labs/o1js/pull/1398 + - this is used to implement `TokenContract` under the hood + +### Fixed + +- Mainnet support. https://github.com/o1-labs/o1js/pull/1437 + +## [0.16.0](https://github.com/o1-labs/o1js/compare/e5d1e0f...834a44002) + +### Breaking changes + +- Protocol change that adds a "transaction version" to the permission to set verification keys https://github.com/MinaProtocol/mina/pull/14407 + - See [the relevant RFC](https://github.com/MinaProtocol/mina/blob/9577ad689a8e4d4f97e1d0fc3d26e20219f4abd1/rfcs/0051-verification-key-permissions.md) for the motivation behind this change + - Breaks all deployed contracts, as it changes the account update layout + +### Added + +- Provable type `Packed` to pack small field elements into fewer field elements https://github.com/o1-labs/o1js/pull/1376 +- Provable type `Hashed` to represent provable types by their hash https://github.com/o1-labs/o1js/pull/1377 + - This also exposes `Poseidon.hashPacked()` to efficiently hash an arbitrary type + +### Changed + +- Reduce number of constraints of ECDSA verification by 5% https://github.com/o1-labs/o1js/pull/1376 + +## [0.15.4](https://github.com/o1-labs/o1js/compare/be748e42e...e5d1e0f) + +### Changed + +- Improve performance of Wasm Poseidon hashing by a factor of 13x https://github.com/o1-labs/o1js/pull/1378 + - Speeds up local blockchain tests without proving by ~40% +- Improve performance of Field inverse https://github.com/o1-labs/o1js/pull/1373 + - Speeds up proving by ~2-4% + +### Added + +- Configurable `networkId` when declaring a Mina instance. https://github.com/o1-labs/o1js/pull/1387 + - Defaults to `"testnet"`, the other option is `"mainnet"` + - The `networkId` parameter influences the algorithm used for signatures, and ensures that testnet transactions can't be replayed on mainnet + +## [0.15.3](https://github.com/o1-labs/o1js/compare/1ad7333e9e...be748e42e) + +### Added + +- **SHA256 hash function** exposed via `Hash.SHA2_256` or `Gadgets.SHA256`. https://github.com/o1-labs/o1js/pull/1285 + +### Changed + +- `Mina.accountCreationFee()` is deprecated in favor of `Mina.getNetworkConstants().accountCreationFee`. https://github.com/o1-labs/o1js/pull/1367 + - `Mina.getNetworkConstants()` returns: + - [default](https://github.com/o1-labs/o1js/pull/1367/files#diff-ef2c3547d64a8eaa8253cd82b3623288f3271e14f1dc893a0a3ddc1ff4b9688fR7) network constants if used outside of the transaction scope. + - [actual](https://github.com/o1-labs/o1js/pull/1367/files#diff-437f2c15df7c90ad8154c5de1677ec0838d51859bcc0a0cefd8a0424b5736f31R1051) network constants if used within the transaction scope. + +### Fixed + +- Fix approving of complex account update layouts https://github.com/o1-labs/o1js/pull/1364 + +## [0.15.2](https://github.com/o1-labs/o1js/compare/1ad7333e9e...08ba27329) + +### Fixed + +- Fix bug in `Hash.hash()` which always resulted in an error https://github.com/o1-labs/o1js/pull/1346 + +## [0.15.1](https://github.com/o1-labs/o1js/compare/1ad7333e9e...19115a159) + +### Breaking changes + +- Rename `Gadgets.rotate()` to `Gadgets.rotate64()` to better reflect the amount of bits the gadget operates on. https://github.com/o1-labs/o1js/pull/1259 +- Rename `Gadgets.{leftShift(), rightShift()}` to `Gadgets.{leftShift64(), rightShift64()}` to better reflect the amount of bits the gadget operates on. https://github.com/o1-labs/o1js/pull/1259 + +### Added + +- Non-native elliptic curve operations exposed through `createForeignCurve()` class factory https://github.com/o1-labs/o1js/pull/1007 +- **ECDSA signature verification** exposed through `createEcdsa()` class factory https://github.com/o1-labs/o1js/pull/1240 https://github.com/o1-labs/o1js/pull/1007 https://github.com/o1-labs/o1js/pull/1307 + - For an example, see `./src/examples/crypto/ecdsa` +- **Keccak/SHA3 hash function** exposed on `Keccak` namespace https://github.com/o1-labs/o1js/pull/1291 +- `Hash` namespace which holds all hash functions https://github.com/o1-labs/o1js/pull/999 + - `Bytes`, provable type to hold a byte array, which serves as input and output for Keccak variants + - `UInt8`, provable type to hold a single byte, which is constrained to be in the 0 to 255 range +- `Gadgets.rotate32()` for rotation over 32 bit values https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.leftShift32()` for left shift over 32 bit values https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.divMod32()` division modulo 2^32 that returns the remainder and quotient of the operation https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.rangeCheck32()` range check for 32 bit values https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.addMod32()` addition modulo 2^32 https://github.com/o1-labs/o1js/pull/1259 +- Expose new bitwise gadgets on `UInt32` and `UInt64` https://github.com/o1-labs/o1js/pull/1259 + - bitwise XOR via `{UInt32, UInt64}.xor()` + - bitwise NOT via `{UInt32, UInt64}.not()` + - bitwise ROTATE via `{UInt32, UInt64}.rotate()` + - bitwise LEFTSHIFT via `{UInt32, UInt64}.leftShift()` + - bitwise RIGHTSHIFT via `{UInt32, UInt64}.rightShift()` + - bitwise AND via `{UInt32, UInt64}.and()` +- Example for using actions to store a map data structure https://github.com/o1-labs/o1js/pull/1300 +- `Provable.constraintSystem()` and `{ZkProgram,SmartContract}.analyzeMethods()` return a `summary()` method to return a summary of the constraints used by a method https://github.com/o1-labs/o1js/pull/1007 +- `assert()` asserts that a given statement is true https://github.com/o1-labs/o1js/pull/1285 + +### Fixed + +- Fix stack overflows when calling provable methods with large inputs https://github.com/o1-labs/o1js/pull/1334 +- Fix `Local.setProofsEnabled()` which would not get picked up by `deploy()` https://github.com/o1-labs/o1js/pull/1330 +- Remove usage of private class fields in core types like `Field`, for better type compatibility between different o1js versions https://github.com/o1-labs/o1js/pull/1319 + +## [0.15.0](https://github.com/o1-labs/o1js/compare/1ad7333e9e...7acf19d0d) + +### Breaking changes + +- `ZkProgram.compile()` now returns the verification key and its hash, to be consistent with `SmartContract.compile()` https://github.com/o1-labs/o1js/pull/1292 [@rpanic](https://github.com/rpanic) + +### Added + +- **Foreign field arithmetic** exposed through the `createForeignField()` class factory https://github.com/o1-labs/snarkyjs/pull/985 +- `Crypto` namespace which exposes elliptic curve and finite field arithmetic on bigints, as well as example curve parameters https://github.com/o1-labs/o1js/pull/1240 +- `Gadgets.ForeignField.assertMul()` for efficiently constraining products of sums in non-native arithmetic https://github.com/o1-labs/o1js/pull/1262 +- `Unconstrained` for safely maintaining unconstrained values in provable code https://github.com/o1-labs/o1js/pull/1262 +- `Gadgets.rangeCheck8()` to assert that a value fits in 8 bits https://github.com/o1-labs/o1js/pull/1288 + +### Changed + +- Change precondition APIs to use "require" instead of "assert" as the verb, to distinguish them from provable assertions. [@LuffySama-Dev](https://github.com/LuffySama-Dev) + - `this.x.getAndAssertEquals()` is now `this.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1263 + - `this.x.assertEquals(x)` is now `this.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1263 + - `this.account.x.getAndAssertEquals(x)` is now `this.account.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1265 + - `this.account.x.assertBetween()` is now `this.account.x.requireBetween()` https://github.com/o1-labs/o1js/pull/1265 + - `this.network.x.getAndAssertEquals()` is now `this.network.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1265 +- `Provable.constraintSystem()` and `{ZkProgram,SmartContract}.analyzeMethods()` return a `print()` method for pretty-printing the constraint system https://github.com/o1-labs/o1js/pull/1240 + +### Fixed + +- Fix missing recursive verification of proofs in smart contracts https://github.com/o1-labs/o1js/pull/1302 + +## [0.14.2](https://github.com/o1-labs/o1js/compare/26363465d...1ad7333e9e) + +### Breaking changes + +- Change return signature of `ZkProgram.analyzeMethods()` to be a keyed object https://github.com/o1-labs/o1js/pull/1223 + +### Added + +- Provable non-native field arithmetic: + - `Gadgets.ForeignField.{add, sub, sumchain}()` for addition and subtraction https://github.com/o1-labs/o1js/pull/1220 + - `Gadgets.ForeignField.{mul, inv, div}()` for multiplication and division https://github.com/o1-labs/o1js/pull/1223 +- Comprehensive internal testing of constraint system layouts generated by new gadgets https://github.com/o1-labs/o1js/pull/1241 https://github.com/o1-labs/o1js/pull/1220 + +### Changed + +- `Lightnet` namespace API updates with added `listAcquiredKeyPairs()` method https://github.com/o1-labs/o1js/pull/1256 +- Expose raw provable methods of a `ZkProgram` on `zkProgram.rawMethods` https://github.com/o1-labs/o1js/pull/1241 +- Reduce number of constraints needed by `rotate()`, `leftShift()` and, `rightShift()` gadgets https://github.com/o1-labs/o1js/pull/1201 + +### Fixed + +- Add a parameter to `checkZkappTransaction` for block length to check for transaction inclusion. This fixes a case where `Transaction.wait()` only checked the latest block, which led to an error once the transaction was included in a block that was not the latest. https://github.com/o1-labs/o1js/pull/1239 + +## [0.14.1](https://github.com/o1-labs/o1js/compare/e8e7510e1...26363465d) + +### Added + +- `Gadgets.not()`, new provable method to support bitwise not. https://github.com/o1-labs/o1js/pull/1198 +- `Gadgets.leftShift() / Gadgets.rightShift()`, new provable methods to support bitwise shifting. https://github.com/o1-labs/o1js/pull/1194 +- `Gadgets.and()`, new provable method to support bitwise and. https://github.com/o1-labs/o1js/pull/1193 +- `Gadgets.multiRangeCheck()` and `Gadgets.compactMultiRangeCheck()`, two building blocks for non-native arithmetic with bigints of size up to 264 bits. https://github.com/o1-labs/o1js/pull/1216 + +### Fixed + +- Removed array reversal of fetched actions, since they are returned in the correct order. https://github.com/o1-labs/o1js/pull/1258 + +## [0.14.0](https://github.com/o1-labs/o1js/compare/045faa7...e8e7510e1) + +### Breaking changes + +- Constraint optimizations in Field methods and core crypto changes break all verification keys https://github.com/o1-labs/o1js/pull/1171 https://github.com/o1-labs/o1js/pull/1178 + +### Changed + +- `ZkProgram` has moved out of the `Experimental` namespace and is now available as a top-level import directly. `Experimental.ZkProgram` has been deprecated. +- `ZkProgram` gets a new input argument `name: string` which is required in the non-experimental API. The name is used to identify a ZkProgram when caching prover keys. https://github.com/o1-labs/o1js/pull/1200 + +### Added + +- `Lightnet` namespace to interact with the account manager provided by the [lightnet Mina network](https://hub.docker.com/r/o1labs/mina-local-network) https://github.com/o1-labs/o1js/pull/1167 +- Internal support for several custom gates (range check, bitwise operations, foreign field operations) and lookup tables https://github.com/o1-labs/o1js/pull/1176 +- `Gadgets.rangeCheck64()`, new provable method to do efficient 64-bit range checks using lookup tables https://github.com/o1-labs/o1js/pull/1181 +- `Gadgets.rotate()`, new provable method to support bitwise rotation for native field elements. https://github.com/o1-labs/o1js/pull/1182 +- `Gadgets.xor()`, new provable method to support bitwise xor for native field elements. https://github.com/o1-labs/o1js/pull/1177 +- `Proof.dummy()` to create dummy proofs https://github.com/o1-labs/o1js/pull/1188 + - You can use this to write ZkPrograms that handle the base case and the inductive case in the same method. + +### Changed + +- Use cached prover keys in `compile()` when running in Node.js https://github.com/o1-labs/o1js/pull/1187 + - Caching is configurable by passing a custom `Cache` (new export) to `compile()` + - By default, prover keys are stored in an OS-dependent cache directory; `~/.cache/pickles` on Mac and Linux +- Use cached setup points (SRS and Lagrange bases) when running in Node.js https://github.com/o1-labs/o1js/pull/1197 + - Also, speed up SRS generation by using multiple threads + - Together with caching of prover keys, this speeds up compilation time by roughly + - **86%** when everything is cached + - **34%** when nothing is cached + +## [0.13.1](https://github.com/o1-labs/o1js/compare/c2f392fe5...045faa7) + +### Breaking changes + +- Changes to some verification keys caused by changing the way `Struct` orders object properties. https://github.com/o1-labs/o1js/pull/1124 [@Comdex](https://github.com/Comdex) + - To recover existing verification keys and behavior, change the order of properties in your Struct definitions to be alphabetical + - The `customObjectKeys` option is removed from `Struct` + +### Changed + +- Improve prover performance by ~25% https://github.com/o1-labs/o1js/pull/1092 + - Change internal representation of field elements to be JS bigint instead of Uint8Array +- Consolidate internal framework for testing equivalence of two implementations + +## [0.13.0](https://github.com/o1-labs/o1js/compare/fbd4b2717...c2f392fe5) + +### Breaking changes + +- Changes to verification keys caused by updates to the proof system. This breaks all deployed contracts https://github.com/o1-labs/o1js/pull/1016 + +## [0.12.2](https://github.com/o1-labs/o1js/compare/b1d8d5910...fbd4b2717) + +### Changed + +- Renamed SnarkyJS to o1js https://github.com/o1-labs/o1js/pull/1104 +- Reduce loading time of the library by 3-4x https://github.com/o1-labs/o1js/pull/1073 +- Improve error when forgetting `transaction.prove()` https://github.com/o1-labs/o1js/pull/1095 + +## [0.12.1](https://github.com/o1-labs/o1js/compare/161b69d602...b1d8d5910) ### Added -- Added a method `createTestNullifier` to the Nullifier class for testing purposes. It is recommended to use mina-signer to create Nullifiers in production, since it does not leak the private key of the user. The `Nullifier.createTestNullifier` method requires the private key as an input _outside of the users wallet_. https://github.com/o1-labs/snarkyjs/pull/1026 -- Added `field.isEven` to check if a Field element is odd or even. https://github.com/o1-labs/snarkyjs/pull/1026 +- Added a method `createTestNullifier` to the Nullifier class for testing purposes. It is recommended to use mina-signer to create Nullifiers in production, since it does not leak the private key of the user. The `Nullifier.createTestNullifier` method requires the private key as an input _outside of the users wallet_. https://github.com/o1-labs/o1js/pull/1026 +- Added `field.isEven` to check if a Field element is odd or even. https://github.com/o1-labs/o1js/pull/1026 ### Fixed -- Revert verification key hash change from previous release to stay compatible with the current testnet https://github.com/o1-labs/snarkyjs/pull/1032 +- Revert verification key hash change from previous release to stay compatible with the current testnet https://github.com/o1-labs/o1js/pull/1032 -## [0.12.0](https://github.com/o1-labs/snarkyjs/compare/eaa39dca0...161b69d602) +## [0.12.0](https://github.com/o1-labs/o1js/compare/eaa39dca0...161b69d602) ### Breaking Changes -- Fix the default verification key hash that was generated for AccountUpdates. This change adopts the default mechanism provided by Mina Protocol https://github.com/o1-labs/snarkyjs/pull/1021 +- Fix the default verification key hash that was generated for AccountUpdates. This change adopts the default mechanism provided by Mina Protocol https://github.com/o1-labs/o1js/pull/1021 - Please be aware that this alteration results in a breaking change affecting the verification key of already deployed contracts. -## [0.11.4](https://github.com/o1-labs/snarkyjs/compare/544489609...eaa39dca0) +## [0.11.4](https://github.com/o1-labs/o1js/compare/544489609...eaa39dca0) ### Fixed -- NodeJS error caused by invalid import https://github.com/o1-labs/snarkyjs/issues/1012 +- NodeJS error caused by invalid import https://github.com/o1-labs/o1js/issues/1012 -## [0.11.3](https://github.com/o1-labs/snarkyjs/compare/2d2af219c...544489609) +## [0.11.3](https://github.com/o1-labs/o1js/compare/2d2af219c...544489609) ### Fixed -- Fix commonJS version of SnarkyJS, again https://github.com/o1-labs/snarkyjs/pull/1006 +- Fix commonJS version of o1js, again https://github.com/o1-labs/o1js/pull/1006 -## [0.11.2](https://github.com/o1-labs/snarkyjs/compare/c549e02fa...2d2af219c) +## [0.11.2](https://github.com/o1-labs/o1js/compare/c549e02fa...2d2af219c) ### Fixed -- Fix commonJS version of SnarkyJS https://github.com/o1-labs/snarkyjs/pull/1005 +- Fix commonJS version of o1js https://github.com/o1-labs/o1js/pull/1005 -## [0.11.1](https://github.com/o1-labs/snarkyjs/compare/3fbd9678e...c549e02fa) +## [0.11.1](https://github.com/o1-labs/o1js/compare/3fbd9678e...c549e02fa) ### Breaking changes -- `Group` operations now generate a different set of constraints. This breaks deployed contracts, because the circuit changed. https://github.com/o1-labs/snarkyjs/pull/967 +- `Group` operations now generate a different set of constraints. This breaks deployed contracts, because the circuit changed. https://github.com/o1-labs/o1js/pull/967 ### Added -- Implemented `Nullifier` as a new primitive https://github.com/o1-labs/snarkyjs/pull/882 +- Implemented `Nullifier` as a new primitive https://github.com/o1-labs/o1js/pull/882 - mina-signer can now be used to generate a Nullifier, which can be consumed by zkApps using the newly added Nullifier Struct ### Changed -- Improve error message `Can't evaluate prover code outside an as_prover block` https://github.com/o1-labs/snarkyjs/pull/998 +- Improve error message `Can't evaluate prover code outside an as_prover block` https://github.com/o1-labs/o1js/pull/998 ### Fixed -- Fix unsupported use of `window` when running SnarkyJS in workers https://github.com/o1-labs/snarkyjs/pull/1002 +- Fix unsupported use of `window` when running o1js in workers https://github.com/o1-labs/o1js/pull/1002 -## [0.11.0](https://github.com/o1-labs/snarkyjs/compare/a632313a...3fbd9678e) +## [0.11.0](https://github.com/o1-labs/o1js/compare/a632313a...3fbd9678e) ### Breaking changes -- Rewrite of `Provable.if()` causes breaking changes to all deployed contracts https://github.com/o1-labs/snarkyjs/pull/889 -- Remove all deprecated methods and properties on `Field` https://github.com/o1-labs/snarkyjs/pull/902 -- The `Field(x)` constructor and other Field methods no longer accept a `boolean` as input. Instead, you can now pass in a `bigint` to all Field methods. https://github.com/o1-labs/snarkyjs/pull/902 -- Remove redundant `signFeePayer()` method https://github.com/o1-labs/snarkyjs/pull/935 +- Rewrite of `Provable.if()` causes breaking changes to all deployed contracts https://github.com/o1-labs/o1js/pull/889 +- Remove all deprecated methods and properties on `Field` https://github.com/o1-labs/o1js/pull/902 +- The `Field(x)` constructor and other Field methods no longer accept a `boolean` as input. Instead, you can now pass in a `bigint` to all Field methods. https://github.com/o1-labs/o1js/pull/902 +- Remove redundant `signFeePayer()` method https://github.com/o1-labs/o1js/pull/935 ### Added -- Add `field.assertNotEquals()` to assert that a field element does not equal some value https://github.com/o1-labs/snarkyjs/pull/902 +- Add `field.assertNotEquals()` to assert that a field element does not equal some value https://github.com/o1-labs/o1js/pull/902 - More efficient than `field.equals(x).assertFalse()` -- Add `scalar.toConstant()`, `scalar.toBigInt()`, `Scalar.from()`, `privateKey.toBigInt()`, `PrivateKey.fromBigInt()` https://github.com/o1-labs/snarkyjs/pull/935 -- `Poseidon.hashToGroup` enables hashing to a group https://github.com/o1-labs/snarkyjs/pull/887 +- Add `scalar.toConstant()`, `scalar.toBigInt()`, `Scalar.from()`, `privateKey.toBigInt()`, `PrivateKey.fromBigInt()` https://github.com/o1-labs/o1js/pull/935 +- `Poseidon.hashToGroup` enables hashing to a group https://github.com/o1-labs/o1js/pull/887 ### Changed -- **Make stack traces more readable** https://github.com/o1-labs/snarkyjs/pull/890 - - Stack traces thrown from SnarkyJS are cleaned up by filtering out unnecessary lines and other noisy details -- Remove optional `zkappKey` argument in `smartContract.init()`, and instead assert that `provedState` is false when `init()` is called https://github.com/o1-labs/snarkyjs/pull/908 -- Improve assertion error messages on `Field` methods https://github.com/o1-labs/snarkyjs/issues/743 https://github.com/o1-labs/snarkyjs/pull/902 -- Publicly expose the internal details of the `Field` type https://github.com/o1-labs/snarkyjs/pull/902 +- **Make stack traces more readable** https://github.com/o1-labs/o1js/pull/890 + - Stack traces thrown from o1js are cleaned up by filtering out unnecessary lines and other noisy details +- Remove optional `zkappKey` argument in `smartContract.init()`, and instead assert that `provedState` is false when `init()` is called https://github.com/o1-labs/o1js/pull/908 +- Improve assertion error messages on `Field` methods https://github.com/o1-labs/o1js/issues/743 https://github.com/o1-labs/o1js/pull/902 +- Publicly expose the internal details of the `Field` type https://github.com/o1-labs/o1js/pull/902 ### Deprecated -- Utility methods on `Circuit` are deprecated in favor of the same methods on `Provable` https://github.com/o1-labs/snarkyjs/pull/889 +- Utility methods on `Circuit` are deprecated in favor of the same methods on `Provable` https://github.com/o1-labs/o1js/pull/889 - `Circuit.if()`, `Circuit.witness()`, `Circuit.log()` and others replaced by `Provable.if()`, `Provable.witness()`, `Provable.log()` - Under the hood, some of these methods were rewritten in TypeScript -- Deprecate `field.isZero()` https://github.com/o1-labs/snarkyjs/pull/902 +- Deprecate `field.isZero()` https://github.com/o1-labs/o1js/pull/902 ### Fixed -- Fix running SnarkyJS in Node.js on Windows https://github.com/o1-labs/snarkyjs-bindings/pull/19 [@wizicer](https://github.com/wizicer) -- Fix error reporting from GraphQL requests https://github.com/o1-labs/snarkyjs/pull/919 -- Resolved an `Out of Memory error` experienced on iOS devices (iPhones and iPads) during the initialization of the WASM memory https://github.com/o1-labs/snarkyjs-bindings/pull/26 -- Fix `field.greaterThan()` and other comparison methods outside provable code https://github.com/o1-labs/snarkyjs/issues/858 https://github.com/o1-labs/snarkyjs/pull/902 -- Fix `field.assertBool()` https://github.com/o1-labs/snarkyjs/issues/469 https://github.com/o1-labs/snarkyjs/pull/902 -- Fix `Field(bigint)` where `bigint` is larger than the field modulus https://github.com/o1-labs/snarkyjs/issues/432 https://github.com/o1-labs/snarkyjs/pull/902 +- Fix running o1js in Node.js on Windows https://github.com/o1-labs/o1js-bindings/pull/19 [@wizicer](https://github.com/wizicer) +- Fix error reporting from GraphQL requests https://github.com/o1-labs/o1js/pull/919 +- Resolved an `Out of Memory error` experienced on iOS devices (iPhones and iPads) during the initialization of the WASM memory https://github.com/o1-labs/o1js-bindings/pull/26 +- Fix `field.greaterThan()` and other comparison methods outside provable code https://github.com/o1-labs/o1js/issues/858 https://github.com/o1-labs/o1js/pull/902 +- Fix `field.assertBool()` https://github.com/o1-labs/o1js/issues/469 https://github.com/o1-labs/o1js/pull/902 +- Fix `Field(bigint)` where `bigint` is larger than the field modulus https://github.com/o1-labs/o1js/issues/432 https://github.com/o1-labs/o1js/pull/902 - The new behaviour is to use the modular residual of the input -- No longer fail on missing signature in `tx.send()`. This fixes the flow of deploying a zkApp from a UI via a wallet https://github.com/o1-labs/snarkyjs/pull/931 [@marekyggdrasil](https://github.com/marekyggdrasil) +- No longer fail on missing signature in `tx.send()`. This fixes the flow of deploying a zkApp from a UI via a wallet https://github.com/o1-labs/o1js/pull/931 [@marekyggdrasil](https://github.com/marekyggdrasil) -## [0.10.1](https://github.com/o1-labs/snarkyjs/compare/bcc666f2...a632313a) +## [0.10.1](https://github.com/o1-labs/o1js/compare/bcc666f2...a632313a) ### Changed -- Allow ZkPrograms to return their public output https://github.com/o1-labs/snarkyjs/pull/874 https://github.com/o1-labs/snarkyjs/pull/876 +- Allow ZkPrograms to return their public output https://github.com/o1-labs/o1js/pull/874 https://github.com/o1-labs/o1js/pull/876 - new option `ZkProgram({ publicOutput?: Provable, ... })`; `publicOutput` has to match the _return type_ of all ZkProgram methods. - the `publicInput` option becomes optional; if not provided, methods no longer expect the public input as first argument - - full usage example: https://github.com/o1-labs/snarkyjs/blob/f95cf2903e97292df9e703b74ee1fc3825df826d/src/examples/program.ts + - full usage example: https://github.com/o1-labs/o1js/blob/f95cf2903e97292df9e703b74ee1fc3825df826d/src/examples/program.ts -## [0.10.0](https://github.com/o1-labs/snarkyjs/compare/97e393ed...bcc666f2) +## [0.10.0](https://github.com/o1-labs/o1js/compare/97e393ed...bcc666f2) ### Breaking Changes -- All references to `actionsHash` are renamed to `actionState` to better mirror what is used in Mina protocol APIs https://github.com/o1-labs/snarkyjs/pull/833 +- All references to `actionsHash` are renamed to `actionState` to better mirror what is used in Mina protocol APIs https://github.com/o1-labs/o1js/pull/833 - This change affects function parameters and returned object keys throughout the API -- No longer make `MayUseToken.InheritFromParent` the default `mayUseToken` value on the caller if one zkApp method calls another one; this removes the need to manually override `mayUseToken` in several known cases https://github.com/o1-labs/snarkyjs/pull/863 +- No longer make `MayUseToken.InheritFromParent` the default `mayUseToken` value on the caller if one zkApp method calls another one; this removes the need to manually override `mayUseToken` in several known cases https://github.com/o1-labs/o1js/pull/863 - Causes a breaking change to the verification key of deployed contracts that use zkApp composability ### Added -- `this.state.getAndAssertEquals()` as a shortcut for `let x = this.state.get(); this.state.assertEquals(x);` https://github.com/o1-labs/snarkyjs/pull/863 +- `this.state.getAndAssertEquals()` as a shortcut for `let x = this.state.get(); this.state.assertEquals(x);` https://github.com/o1-labs/o1js/pull/863 - also added `.getAndAssertEquals()` on `this.account` and `this.network` fields -- Support for fallback endpoints when making network requests, allowing users to provide an array of endpoints for GraphQL network requests. https://github.com/o1-labs/snarkyjs/pull/871 +- Support for fallback endpoints when making network requests, allowing users to provide an array of endpoints for GraphQL network requests. https://github.com/o1-labs/o1js/pull/871 - Endpoints are fetched two at a time, and the result returned from the faster response -- `reducer.forEach(actions, ...)` as a shortcut for `reducer.reduce()` when you don't need a `state` https://github.com/o1-labs/snarkyjs/pull/863 -- New export `TokenId` which supersedes `Token.Id`; `TokenId.deriveId()` replaces `Token.Id.getId()` https://github.com/o1-labs/snarkyjs/pull/863 -- Add `Permissions.allImpossible()` for the set of permissions where nothing is allowed (more convenient than `Permissions.default()` when you want to make most actions impossible) https://github.com/o1-labs/snarkyjs/pull/863 +- `reducer.forEach(actions, ...)` as a shortcut for `reducer.reduce()` when you don't need a `state` https://github.com/o1-labs/o1js/pull/863 +- New export `TokenId` which supersedes `Token.Id`; `TokenId.deriveId()` replaces `Token.Id.getId()` https://github.com/o1-labs/o1js/pull/863 +- Add `Permissions.allImpossible()` for the set of permissions where nothing is allowed (more convenient than `Permissions.default()` when you want to make most actions impossible) https://github.com/o1-labs/o1js/pull/863 ### Changed -- **Massive improvement of memory consumption**, thanks to a refactor of SnarkyJS' worker usage https://github.com/o1-labs/snarkyjs/pull/872 - - Memory reduced by up to 10x; see [the PR](https://github.com/o1-labs/snarkyjs/pull/872) for details +- **Massive improvement of memory consumption**, thanks to a refactor of o1js' worker usage https://github.com/o1-labs/o1js/pull/872 + - Memory reduced by up to 10x; see [the PR](https://github.com/o1-labs/o1js/pull/872) for details - Side effect: `Circuit` API becomes async, for example `MyCircuit.prove(...)` becomes `await MyCircuit.prove(...)` -- Token APIs `this.token.{send,burn,mint}()` now accept an `AccountUpdate` or `SmartContract` as from / to input https://github.com/o1-labs/snarkyjs/pull/863 -- Improve `Transaction.toPretty()` output by adding account update labels in most methods that create account updates https://github.com/o1-labs/snarkyjs/pull/863 -- Raises the limit of actions/events per transaction from 16 to 100, providing users with the ability to submit a larger number of events/actions in a single transaction. https://github.com/o1-labs/snarkyjs/pull/883. +- Token APIs `this.token.{send,burn,mint}()` now accept an `AccountUpdate` or `SmartContract` as from / to input https://github.com/o1-labs/o1js/pull/863 +- Improve `Transaction.toPretty()` output by adding account update labels in most methods that create account updates https://github.com/o1-labs/o1js/pull/863 +- Raises the limit of actions/events per transaction from 16 to 100, providing users with the ability to submit a larger number of events/actions in a single transaction. https://github.com/o1-labs/o1js/pull/883. ### Deprecated -- Deprecate both `shutdown()` and `await isReady`, which are no longer needed https://github.com/o1-labs/snarkyjs/pull/872 +- Deprecate both `shutdown()` and `await isReady`, which are no longer needed https://github.com/o1-labs/o1js/pull/872 ### Fixed -- `SmartContract.deploy()` now throws an error when no verification key is found https://github.com/o1-labs/snarkyjs/pull/885 +- `SmartContract.deploy()` now throws an error when no verification key is found https://github.com/o1-labs/o1js/pull/885 - The old, confusing behaviour was to silently not update the verification key (but still update some permissions to "proof", breaking the zkApp) -## [0.9.8](https://github.com/o1-labs/snarkyjs/compare/1a984089...97e393ed) +## [0.9.8](https://github.com/o1-labs/o1js/compare/1a984089...97e393ed) ### Fixed -- Fix fetching the `access` permission on accounts https://github.com/o1-labs/snarkyjs/pull/851 -- Fix `fetchActions` https://github.com/o1-labs/snarkyjs/pull/844 https://github.com/o1-labs/snarkyjs/pull/854 [@Comdex](https://github.com/Comdex) -- Updated `Mina.TransactionId.isSuccess` to accurately verify zkApp transaction status after using `Mina.TransactionId.wait()`. https://github.com/o1-labs/snarkyjs/pull/826 +- Fix fetching the `access` permission on accounts https://github.com/o1-labs/o1js/pull/851 +- Fix `fetchActions` https://github.com/o1-labs/o1js/pull/844 https://github.com/o1-labs/o1js/pull/854 [@Comdex](https://github.com/Comdex) +- Updated `Mina.TransactionId.isSuccess` to accurately verify zkApp transaction status after using `Mina.TransactionId.wait()`. https://github.com/o1-labs/o1js/pull/826 - This change ensures that the function correctly checks for transaction completion and provides the expected result. -## [0.9.7](https://github.com/o1-labs/snarkyjs/compare/0b7a9ad...1a984089) +## [0.9.7](https://github.com/o1-labs/o1js/compare/0b7a9ad...1a984089) ### Added -- `smartContract.fetchActions()` and `Mina.fetchActions()`, asynchronous methods to fetch actions directly from an archive node https://github.com/o1-labs/snarkyjs/pull/843 [@Comdex](https://github.com/Comdex) +- `smartContract.fetchActions()` and `Mina.fetchActions()`, asynchronous methods to fetch actions directly from an archive node https://github.com/o1-labs/o1js/pull/843 [@Comdex](https://github.com/Comdex) ### Changed -- `Circuit.runAndCheck()` now uses `snarky` to create a constraint system and witnesses, and check constraints. It closely matches behavior during proving and can be used to test provable code without having to create an expensive proof https://github.com/o1-labs/snarkyjs/pull/840 +- `Circuit.runAndCheck()` now uses `snarky` to create a constraint system and witnesses, and check constraints. It closely matches behavior during proving and can be used to test provable code without having to create an expensive proof https://github.com/o1-labs/o1js/pull/840 ### Fixed -- Fixes two issues that were temporarily reintroduced in the 0.9.6 release https://github.com/o1-labs/snarkyjs/issues/799 https://github.com/o1-labs/snarkyjs/issues/530 +- Fixes two issues that were temporarily reintroduced in the 0.9.6 release https://github.com/o1-labs/o1js/issues/799 https://github.com/o1-labs/o1js/issues/530 -## [0.9.6](https://github.com/o1-labs/snarkyjs/compare/21de489...0b7a9ad) +## [0.9.6](https://github.com/o1-labs/o1js/compare/21de489...0b7a9ad) ### Breaking changes - Circuits changed due to an internal rename of "sequence events" to "actions" which included a change to some hash prefixes; this breaks all deployed contracts. - Temporarily reintroduces 2 known issues as a result of reverting a fix necessary for network redeployment: - - https://github.com/o1-labs/snarkyjs/issues/799 - - https://github.com/o1-labs/snarkyjs/issues/530 + - https://github.com/o1-labs/o1js/issues/799 + - https://github.com/o1-labs/o1js/issues/530 - Please note that we plan to address these issues in a future release. In the meantime, to work around this breaking change, you can try calling `fetchAccount` for each account involved in a transaction before executing the `Mina.transaction` block. -- Improve number of constraints needed for Merkle tree hashing https://github.com/o1-labs/snarkyjs/pull/820 +- Improve number of constraints needed for Merkle tree hashing https://github.com/o1-labs/o1js/pull/820 - This breaks deployed zkApps which use `MerkleWitness.calculateRoot()`, because the circuit is changed - You can make your existing contracts compatible again by switching to `MerkleWitness.calculateRootSlow()`, which has the old circuit -- Renamed function parameters: The `getAction` function now accepts a new object structure for its parameters. https://github.com/o1-labs/snarkyjs/pull/828 +- Renamed function parameters: The `getAction` function now accepts a new object structure for its parameters. https://github.com/o1-labs/o1js/pull/828 - The previous object keys, `fromActionHash` and `endActionHash`, have been replaced by `fromActionState` and `endActionState`. ### Added -- `zkProgram.analyzeMethods()` to obtain metadata about a ZkProgram's methods https://github.com/o1-labs/snarkyjs/pull/829 [@maht0rz](https://github.com/maht0rz) +- `zkProgram.analyzeMethods()` to obtain metadata about a ZkProgram's methods https://github.com/o1-labs/o1js/pull/829 [@maht0rz](https://github.com/maht0rz) ### Fixed -- Improved Event Handling in SnarkyJS https://github.com/o1-labs/snarkyjs/pull/825 +- Improved Event Handling in o1js https://github.com/o1-labs/o1js/pull/825 - Updated the internal event type to better handle events emitted in different zkApp transactions and when multiple zkApp transactions are present within a block. - The internal event type now includes event data and transaction information as separate objects, allowing for more accurate information about each event and its associated transaction. -- Removed multiple best tip blocks when fetching action data https://github.com/o1-labs/snarkyjs/pull/817 +- Removed multiple best tip blocks when fetching action data https://github.com/o1-labs/o1js/pull/817 - Implemented a temporary fix that filters out multiple best tip blocks, if they exist, while fetching actions. This fix will be removed once the related issue in the Archive-Node-API repository (https://github.com/o1-labs/Archive-Node-API/issues/7) is resolved. -- New `fromActionState` and `endActionState` parameters for fetchActions function in SnarkyJS https://github.com/o1-labs/snarkyjs/pull/828 +- New `fromActionState` and `endActionState` parameters for fetchActions function in o1js https://github.com/o1-labs/o1js/pull/828 - Allows fetching only necessary actions to compute the latest actions state - Eliminates the need to retrieve the entire actions history of a zkApp - Utilizes `actionStateTwo` field returned by Archive Node API as a safe starting point for deriving the most recent action hash -## [0.9.5](https://github.com/o1-labs/snarkyjs/compare/21de489...4573252d) +## [0.9.5](https://github.com/o1-labs/o1js/compare/21de489...4573252d) -- Update the zkApp verification key from within one of its own methods, via proof https://github.com/o1-labs/snarkyjs/pull/812 +- Update the zkApp verification key from within one of its own methods, via proof https://github.com/o1-labs/o1js/pull/812 ### Breaking changes -- Change type of verification key returned by `SmartContract.compile()` to match `VerificationKey` https://github.com/o1-labs/snarkyjs/pull/812 +- Change type of verification key returned by `SmartContract.compile()` to match `VerificationKey` https://github.com/o1-labs/o1js/pull/812 ### Fixed -- Failing `Mina.transaction` on Berkeley because of unsatisfied constraints caused by dummy data before we fetched account state https://github.com/o1-labs/snarkyjs/pull/807 +- Failing `Mina.transaction` on Berkeley because of unsatisfied constraints caused by dummy data before we fetched account state https://github.com/o1-labs/o1js/pull/807 - Previously, you could work around this by calling `fetchAccount()` for every account invovled in a transaction. This is not necessary anymore. -- Update the zkApp verification key from within one of its own methods, via proof https://github.com/o1-labs/snarkyjs/pull/812 +- Update the zkApp verification key from within one of its own methods, via proof https://github.com/o1-labs/o1js/pull/812 -## [0.9.4](https://github.com/o1-labs/snarkyjs/compare/9acec55...21de489) +## [0.9.4](https://github.com/o1-labs/o1js/compare/9acec55...21de489) ### Fixed -- `getActions` to handle multiple actions with multiple Account Updates https://github.com/o1-labs/snarkyjs/pull/801 +- `getActions` to handle multiple actions with multiple Account Updates https://github.com/o1-labs/o1js/pull/801 -## [0.9.3](https://github.com/o1-labs/snarkyjs/compare/1abdfb70...9acec55) +## [0.9.3](https://github.com/o1-labs/o1js/compare/1abdfb70...9acec55) ### Added -- Use `fetchEvents()` to fetch events for a specified zkApp from a GraphQL endpoint that implements [this schema](https://github.com/o1-labs/Archive-Node-API/blob/efebc9fd3cfc028f536ae2125e0d2676e2b86cd2/src/schema.ts#L1). `Mina.Network` accepts an additional endpoint which points to a GraphQL server. https://github.com/o1-labs/snarkyjs/pull/749 +- Use `fetchEvents()` to fetch events for a specified zkApp from a GraphQL endpoint that implements [this schema](https://github.com/o1-labs/Archive-Node-API/blob/efebc9fd3cfc028f536ae2125e0d2676e2b86cd2/src/schema.ts#L1). `Mina.Network` accepts an additional endpoint which points to a GraphQL server. https://github.com/o1-labs/o1js/pull/749 - Use the `mina` property for the Mina node. - Use `archive` for the archive node. -- Use `getActions` to fetch actions for a specified zkApp from a GraphQL endpoint GraphQL endpoint that implements the same schema as `fetchEvents`. https://github.com/o1-labs/snarkyjs/pull/788 +- Use `getActions` to fetch actions for a specified zkApp from a GraphQL endpoint GraphQL endpoint that implements the same schema as `fetchEvents`. https://github.com/o1-labs/o1js/pull/788 ### Fixed -- Added the missing export of `Mina.TransactionId` https://github.com/o1-labs/snarkyjs/pull/785 -- Added an option to specify `tokenId` as `Field` in `fetchAccount()` https://github.com/o1-labs/snarkyjs/pull/787 [@rpanic](https://github.com/rpanic) +- Added the missing export of `Mina.TransactionId` https://github.com/o1-labs/o1js/pull/785 +- Added an option to specify `tokenId` as `Field` in `fetchAccount()` https://github.com/o1-labs/o1js/pull/787 [@rpanic](https://github.com/rpanic) -## [0.9.2](https://github.com/o1-labs/snarkyjs/compare/9c44b9c2...1abdfb70) +## [0.9.2](https://github.com/o1-labs/o1js/compare/9c44b9c2...1abdfb70) ### Added -- `this.network.timestamp` is added back and is implemented on top of `this.network.globalSlotSinceGenesis` https://github.com/o1-labs/snarkyjs/pull/755 +- `this.network.timestamp` is added back and is implemented on top of `this.network.globalSlotSinceGenesis` https://github.com/o1-labs/o1js/pull/755 ### Changed -- On-chain value `globalSlot` is replaced by the clearer `currentSlot` https://github.com/o1-labs/snarkyjs/pull/755 +- On-chain value `globalSlot` is replaced by the clearer `currentSlot` https://github.com/o1-labs/o1js/pull/755 - `currentSlot` refers to the slot at which the transaction _will be included in a block_. - the only supported method is `currentSlot.assertBetween()` because `currentSlot.get()` is impossible to implement since the value is determined in the future and `currentSlot.assertEquals()` is error-prone ### Fixed -- Incorrect counting of limit on events and actions https://github.com/o1-labs/snarkyjs/pull/758 -- Type error when using `Circuit.array` in on-chain state or events https://github.com/o1-labs/snarkyjs/pull/758 -- Bug when using `Circuit.witness` outside the prover https://github.com/o1-labs/snarkyjs/pull/774 +- Incorrect counting of limit on events and actions https://github.com/o1-labs/o1js/pull/758 +- Type error when using `Circuit.array` in on-chain state or events https://github.com/o1-labs/o1js/pull/758 +- Bug when using `Circuit.witness` outside the prover https://github.com/o1-labs/o1js/pull/774 -## [0.9.1](https://github.com/o1-labs/snarkyjs/compare/71b6132b...9c44b9c2) +## [0.9.1](https://github.com/o1-labs/o1js/compare/71b6132b...9c44b9c2) ### Fixed -- Bug when using `this..get()` outside a transaction https://github.com/o1-labs/snarkyjs/pull/754 +- Bug when using `this..get()` outside a transaction https://github.com/o1-labs/o1js/pull/754 -## [0.9.0](https://github.com/o1-labs/snarkyjs/compare/c5a36207...71b6132b) +## [0.9.0](https://github.com/o1-labs/o1js/compare/c5a36207...71b6132b) ### Added -- `Transaction.fromJSON` to recover transaction object from JSON https://github.com/o1-labs/snarkyjs/pull/705 -- New precondition: `provedState`, a boolean which is true if the entire on-chain state of this account was last modified by a proof https://github.com/o1-labs/snarkyjs/pull/741 +- `Transaction.fromJSON` to recover transaction object from JSON https://github.com/o1-labs/o1js/pull/705 +- New precondition: `provedState`, a boolean which is true if the entire on-chain state of this account was last modified by a proof https://github.com/o1-labs/o1js/pull/741 - Same API as all preconditions: `this.account.provedState.assertEquals(Bool(true))` - Can be used to assert that the state wasn't tampered with by the zkApp developer using non-contract logic, for example, before deploying the zkApp -- New on-chain value `globalSlot`, to make assertions about the current time https://github.com/o1-labs/snarkyjs/pull/649 +- New on-chain value `globalSlot`, to make assertions about the current time https://github.com/o1-labs/o1js/pull/649 - example: `this.globalSlot.get()`, `this.globalSlot.assertBetween(lower, upper)` - - Replaces `network.timestamp`, `network.globalSlotSinceGenesis` and `network.globalSlotSinceHardFork`. https://github.com/o1-labs/snarkyjs/pull/560 + - Replaces `network.timestamp`, `network.globalSlotSinceGenesis` and `network.globalSlotSinceHardFork`. https://github.com/o1-labs/o1js/pull/560 - New permissions: - - `access` to control whether account updates for this account can be used at all https://github.com/o1-labs/snarkyjs/pull/500 - - `setTiming` to control who can update the account's `timing` field https://github.com/o1-labs/snarkyjs/pull/685 + - `access` to control whether account updates for this account can be used at all https://github.com/o1-labs/o1js/pull/500 + - `setTiming` to control who can update the account's `timing` field https://github.com/o1-labs/o1js/pull/685 - Example: `this.permissions.set({ ...Permissions.default(), access: Permissions.proofOrSignature() })` -- Expose low-level view into the PLONK gates created by a smart contract method https://github.com/o1-labs/snarkyjs/pull/687 +- Expose low-level view into the PLONK gates created by a smart contract method https://github.com/o1-labs/o1js/pull/687 - `MyContract.analyzeMethods()..gates` ### Changed -- BREAKING CHANGE: Modify signature algorithm used by `Signature.{create,verify}` to be compatible with mina-signer https://github.com/o1-labs/snarkyjs/pull/710 +- BREAKING CHANGE: Modify signature algorithm used by `Signature.{create,verify}` to be compatible with mina-signer https://github.com/o1-labs/o1js/pull/710 - Signatures created with mina-signer's `client.signFields()` can now be verified inside a SNARK! - Breaks existing deployed smart contracts which use `Signature.verify()` - BREAKING CHANGE: Circuits changed due to core protocol and cryptography changes; this breaks all deployed contracts. -- BREAKING CHANGE: Change structure of `Account` type which is returned by `Mina.getAccount()` https://github.com/o1-labs/snarkyjs/pull/741 +- BREAKING CHANGE: Change structure of `Account` type which is returned by `Mina.getAccount()` https://github.com/o1-labs/o1js/pull/741 - for example, `account.appState` -> `account.zkapp.appState` - - full new type (exported as `Types.Account`): https://github.com/o1-labs/snarkyjs/blob/0be70cb8ceb423976f348980e9d6238820758cc0/src/provable/gen/transaction.ts#L515 -- Test accounts hard-coded in `LocalBlockchain` now have default permissions, not permissions allowing everything. Fixes some unintuitive behaviour in tests, like requiring no signature when using these accounts to send MINA https://github.com/o1-labs/snarkyjs/issues/638 + - full new type (exported as `Types.Account`): https://github.com/o1-labs/o1js/blob/0be70cb8ceb423976f348980e9d6238820758cc0/src/provable/gen/transaction.ts#L515 +- Test accounts hard-coded in `LocalBlockchain` now have default permissions, not permissions allowing everything. Fixes some unintuitive behaviour in tests, like requiring no signature when using these accounts to send MINA https://github.com/o1-labs/o1js/issues/638 ### Removed -- Preconditions `timestamp` and `globalSlotSinceHardFork` https://github.com/o1-labs/snarkyjs/pull/560 +- Preconditions `timestamp` and `globalSlotSinceHardFork` https://github.com/o1-labs/o1js/pull/560 - `timestamp` is expected to come back as a wrapper for the new `globalSlot` -## [0.8.0](https://github.com/o1-labs/snarkyjs/compare/d880bd6e...c5a36207) +## [0.8.0](https://github.com/o1-labs/o1js/compare/d880bd6e...c5a36207) ### Added -- `this.account..set()` as a unified API to update fields on the account https://github.com/o1-labs/snarkyjs/pull/643 +- `this.account..set()` as a unified API to update fields on the account https://github.com/o1-labs/o1js/pull/643 - covers `permissions`, `verificationKey`, `zkappUri`, `tokenSymbol`, `delegate`, `votingFor` - exists on `SmartContract.account` and `AccountUpdate.account` -- `this.sender` to get the public key of the transaction's sender https://github.com/o1-labs/snarkyjs/pull/652 +- `this.sender` to get the public key of the transaction's sender https://github.com/o1-labs/o1js/pull/652 - To get the sender outside a smart contract, there's now `Mina.sender()` -- `tx.wait()` is now implemented. It waits for the transactions inclusion in a block https://github.com/o1-labs/snarkyjs/pull/645 +- `tx.wait()` is now implemented. It waits for the transactions inclusion in a block https://github.com/o1-labs/o1js/pull/645 - `wait()` also now takes an optional `options` parameter to specify the polling interval or maximum attempts. `wait(options?: { maxAttempts?: number; interval?: number }): Promise;` -- `Circuit.constraintSystemFromKeypair(keypair)` to inspect the circuit at a low level https://github.com/o1-labs/snarkyjs/pull/529 +- `Circuit.constraintSystemFromKeypair(keypair)` to inspect the circuit at a low level https://github.com/o1-labs/o1js/pull/529 - Works with a `keypair` (prover + verifier key) generated with the `Circuit` API -- `Mina.faucet()` can now be used to programmatically fund an address on the testnet, using the faucet provided by faucet.minaprotocol.com https://github.com/o1-labs/snarkyjs/pull/693 +- `Mina.faucet()` can now be used to programmatically fund an address on the testnet, using the faucet provided by faucet.minaprotocol.com https://github.com/o1-labs/o1js/pull/693 ### Changed -- BREAKING CHANGE: Constraint changes in `sign()`, `requireSignature()` and `createSigned()` on `AccountUpdate` / `SmartContract`. _This means that smart contracts using these methods in their proofs won't be able to create valid proofs against old deployed verification keys._ https://github.com/o1-labs/snarkyjs/pull/637 -- `Mina.transaction` now takes a _public key_ as the fee payer argument (passing in a private key is deprecated) https://github.com/o1-labs/snarkyjs/pull/652 +- BREAKING CHANGE: Constraint changes in `sign()`, `requireSignature()` and `createSigned()` on `AccountUpdate` / `SmartContract`. _This means that smart contracts using these methods in their proofs won't be able to create valid proofs against old deployed verification keys._ https://github.com/o1-labs/o1js/pull/637 +- `Mina.transaction` now takes a _public key_ as the fee payer argument (passing in a private key is deprecated) https://github.com/o1-labs/o1js/pull/652 - Before: `Mina.transaction(privateKey, ...)`. Now: `Mina.transaction(publicKey, ...)` - `AccountUpdate.fundNewAccount()` now enables funding multiple accounts at once, and deprecates the `initialBalance` argument -- New option `enforceTransactionLimits` for `LocalBlockchain` (default value: `true`), to disable the enforcement of protocol transaction limits (maximum events, maximum sequence events and enforcing certain layout of `AccountUpdate`s depending on their authorization) https://github.com/o1-labs/snarkyjs/pull/620 -- Change the default `send` permissions (for sending MINA or tokens) that get set when deploying a zkApp, from `signature()` to `proof()` https://github.com/o1-labs/snarkyjs/pull/648 -- Functions for making assertions and comparisons have been renamed to their long form, instead of the initial abbreviation. Old function names have been deprecated https://github.com/o1-labs/snarkyjs/pull/681 +- New option `enforceTransactionLimits` for `LocalBlockchain` (default value: `true`), to disable the enforcement of protocol transaction limits (maximum events, maximum sequence events and enforcing certain layout of `AccountUpdate`s depending on their authorization) https://github.com/o1-labs/o1js/pull/620 +- Change the default `send` permissions (for sending MINA or tokens) that get set when deploying a zkApp, from `signature()` to `proof()` https://github.com/o1-labs/o1js/pull/648 +- Functions for making assertions and comparisons have been renamed to their long form, instead of the initial abbreviation. Old function names have been deprecated https://github.com/o1-labs/o1js/pull/681 - `.lt` -> `.lessThan` - `.lte` -> `.lessThanOrEqual` - `.gt` -> `.greaterThan` @@ -346,206 +668,206 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Deprecated -- `this.setPermissions()` in favor of `this.account.permissions.set()` https://github.com/o1-labs/snarkyjs/pull/643 +- `this.setPermissions()` in favor of `this.account.permissions.set()` https://github.com/o1-labs/o1js/pull/643 - `this.tokenSymbol.set()` in favor of `this.account.tokenSymbol.set()` - `this.setValue()` in favor of `this.account..set()` - `Mina.transaction(privateKey: PrivateKey, ...)` in favor of new signature `Mina.transaction(publicKey: PublicKey, ...)` -- `AccountUpdate.createSigned(privateKey: PrivateKey)` in favor of new signature `AccountUpdate.createSigned(publicKey: PublicKey)` https://github.com/o1-labs/snarkyjs/pull/637 -- `.lt`, `.lte`, `gt`, `gte`, `.assertLt`, `.assertLte`, `.assertGt`, `.assertGte` have been deprecated. https://github.com/o1-labs/snarkyjs/pull/681 +- `AccountUpdate.createSigned(privateKey: PrivateKey)` in favor of new signature `AccountUpdate.createSigned(publicKey: PublicKey)` https://github.com/o1-labs/o1js/pull/637 +- `.lt`, `.lte`, `gt`, `gte`, `.assertLt`, `.assertLte`, `.assertGt`, `.assertGte` have been deprecated. https://github.com/o1-labs/o1js/pull/681 ### Fixed -- Fixed Apple silicon performance issue https://github.com/o1-labs/snarkyjs/issues/491 -- Type inference for Structs with instance methods https://github.com/o1-labs/snarkyjs/pull/567 +- Fixed Apple silicon performance issue https://github.com/o1-labs/o1js/issues/491 +- Type inference for Structs with instance methods https://github.com/o1-labs/o1js/pull/567 - also fixes `Struct.fromJSON` -- `SmartContract.fetchEvents` fixed when multiple event types existed https://github.com/o1-labs/snarkyjs/issues/627 -- Error when using reduce with a `Struct` as state type https://github.com/o1-labs/snarkyjs/pull/689 -- Fix use of stale cached accounts in `Mina.transaction` https://github.com/o1-labs/snarkyjs/issues/430 +- `SmartContract.fetchEvents` fixed when multiple event types existed https://github.com/o1-labs/o1js/issues/627 +- Error when using reduce with a `Struct` as state type https://github.com/o1-labs/o1js/pull/689 +- Fix use of stale cached accounts in `Mina.transaction` https://github.com/o1-labs/o1js/issues/430 -## [0.7.3](https://github.com/o1-labs/snarkyjs/compare/5f20f496...d880bd6e) +## [0.7.3](https://github.com/o1-labs/o1js/compare/5f20f496...d880bd6e) ### Fixed -- Bug in `deploy()` when initializing a contract that already exists https://github.com/o1-labs/snarkyjs/pull/588 +- Bug in `deploy()` when initializing a contract that already exists https://github.com/o1-labs/o1js/pull/588 ### Deprecated -- `Mina.BerkeleyQANet` in favor of the clearer-named `Mina.Network` https://github.com/o1-labs/snarkyjs/pull/588 +- `Mina.BerkeleyQANet` in favor of the clearer-named `Mina.Network` https://github.com/o1-labs/o1js/pull/588 -## [0.7.2](https://github.com/o1-labs/snarkyjs/compare/705f58d3...5f20f496) +## [0.7.2](https://github.com/o1-labs/o1js/compare/705f58d3...5f20f496) ### Added -- `MerkleMap` and `MerkleMapWitness` https://github.com/o1-labs/snarkyjs/pull/546 -- Lots of doc comments! https://github.com/o1-labs/snarkyjs/pull/580 +- `MerkleMap` and `MerkleMapWitness` https://github.com/o1-labs/o1js/pull/546 +- Lots of doc comments! https://github.com/o1-labs/o1js/pull/580 ### Fixed -- Bug in `Circuit.log` printing account updates https://github.com/o1-labs/snarkyjs/pull/578 +- Bug in `Circuit.log` printing account updates https://github.com/o1-labs/o1js/pull/578 -## [0.7.1](https://github.com/o1-labs/snarkyjs/compare/f0837188...705f58d3) +## [0.7.1](https://github.com/o1-labs/o1js/compare/f0837188...705f58d3) ### Fixed -- Testnet-incompatible signatures in v0.7.0 https://github.com/o1-labs/snarkyjs/pull/565 +- Testnet-incompatible signatures in v0.7.0 https://github.com/o1-labs/o1js/pull/565 -## [0.7.0](https://github.com/o1-labs/snarkyjs/compare/f0837188...9a94231c) +## [0.7.0](https://github.com/o1-labs/o1js/compare/f0837188...9a94231c) ### Added -- Added an optional string parameter to certain `assert` methods https://github.com/o1-labs/snarkyjs/pull/470 -- `Struct`, a new primitive for declaring composite, SNARK-compatible types https://github.com/o1-labs/snarkyjs/pull/416 +- Added an optional string parameter to certain `assert` methods https://github.com/o1-labs/o1js/pull/470 +- `Struct`, a new primitive for declaring composite, SNARK-compatible types https://github.com/o1-labs/o1js/pull/416 - With this, we also added a way to include auxiliary, non-field element data in composite types - Added `VerificationKey`, which is a `Struct` with auxiliary data, to pass verification keys to a `@method` - BREAKING CHANGE: Change names related to circuit types: `AsFieldsAndAux` -> `Provable`, `AsFieldElement` -> `ProvablePure`, `circuitValue` -> `provable` - BREAKING CHANGE: Change all `ofFields` and `ofBits` methods on circuit types to `fromFields` and `fromBits` -- New option `proofsEnabled` for `LocalBlockchain` (default value: `true`), to quickly test transaction logic with proofs disabled https://github.com/o1-labs/snarkyjs/pull/462 - - with `proofsEnabled: true`, proofs now get verified locally https://github.com/o1-labs/snarkyjs/pull/423 -- `SmartContract.approve()` to approve a tree of child account updates https://github.com/o1-labs/snarkyjs/pull/428 https://github.com/o1-labs/snarkyjs/pull/534 +- New option `proofsEnabled` for `LocalBlockchain` (default value: `true`), to quickly test transaction logic with proofs disabled https://github.com/o1-labs/o1js/pull/462 + - with `proofsEnabled: true`, proofs now get verified locally https://github.com/o1-labs/o1js/pull/423 +- `SmartContract.approve()` to approve a tree of child account updates https://github.com/o1-labs/o1js/pull/428 https://github.com/o1-labs/o1js/pull/534 - AccountUpdates are now valid `@method` arguments, and `approve()` is intended to be used on them when passed to a method - Also replaces `Experimental.accountUpdateFromCallback()` -- `Circuit.log()` to easily log Fields and other provable types inside a method, with the same API as `console.log()` https://github.com/o1-labs/snarkyjs/pull/484 -- `SmartContract.init()` is a new method on the base `SmartContract` that will be called only during the first deploy (not if you re-deploy later to upgrade the contract) https://github.com/o1-labs/snarkyjs/pull/543 +- `Circuit.log()` to easily log Fields and other provable types inside a method, with the same API as `console.log()` https://github.com/o1-labs/o1js/pull/484 +- `SmartContract.init()` is a new method on the base `SmartContract` that will be called only during the first deploy (not if you re-deploy later to upgrade the contract) https://github.com/o1-labs/o1js/pull/543 - Overriding `init()` is the new recommended way to add custom state initialization logic. -- `transaction.toPretty()` and `accountUpdate.toPretty()` for debugging transactions by printing only the pieces that differ from default account updates https://github.com/o1-labs/snarkyjs/pull/428 -- `AccountUpdate.attachToTransaction()` for explicitly adding an account update to the current transaction. This replaces some previous behaviour where an account update got attached implicitly https://github.com/o1-labs/snarkyjs/pull/484 -- `SmartContract.requireSignature()` and `AccountUpdate.requireSignature()` as a simpler, better-named replacement for `.sign()` https://github.com/o1-labs/snarkyjs/pull/558 +- `transaction.toPretty()` and `accountUpdate.toPretty()` for debugging transactions by printing only the pieces that differ from default account updates https://github.com/o1-labs/o1js/pull/428 +- `AccountUpdate.attachToTransaction()` for explicitly adding an account update to the current transaction. This replaces some previous behaviour where an account update got attached implicitly https://github.com/o1-labs/o1js/pull/484 +- `SmartContract.requireSignature()` and `AccountUpdate.requireSignature()` as a simpler, better-named replacement for `.sign()` https://github.com/o1-labs/o1js/pull/558 ### Changed -- BREAKING CHANGE: `tx.send()` is now asynchronous: old: `send(): TransactionId` new: `send(): Promise` and `tx.send()` now directly waits for the network response, as opposed to `tx.send().wait()` https://github.com/o1-labs/snarkyjs/pull/423 +- BREAKING CHANGE: `tx.send()` is now asynchronous: old: `send(): TransactionId` new: `send(): Promise` and `tx.send()` now directly waits for the network response, as opposed to `tx.send().wait()` https://github.com/o1-labs/o1js/pull/423 - Sending transactions to `LocalBlockchain` now involves -- `Circuit.witness` can now be called outside circuits, where it will just directly return the callback result https://github.com/o1-labs/snarkyjs/pull/484 -- The `FeePayerSpec`, which is used to specify properties of the transaction via `Mina.transaction()`, now has another optional parameter to specify the nonce manually. `Mina.transaction({ feePayerKey: feePayer, nonce: 1 }, () => {})` https://github.com/o1-labs/snarkyjs/pull/497 -- BREAKING CHANGE: Static methods of type `.fromString()`, `.fromNumber()` and `.fromBigInt()` on `Field`, `UInt64`, `UInt32` and `Int64` are no longer supported https://github.com/o1-labs/snarkyjs/pull/519 +- `Circuit.witness` can now be called outside circuits, where it will just directly return the callback result https://github.com/o1-labs/o1js/pull/484 +- The `FeePayerSpec`, which is used to specify properties of the transaction via `Mina.transaction()`, now has another optional parameter to specify the nonce manually. `Mina.transaction({ feePayerKey: feePayer, nonce: 1 }, () => {})` https://github.com/o1-labs/o1js/pull/497 +- BREAKING CHANGE: Static methods of type `.fromString()`, `.fromNumber()` and `.fromBigInt()` on `Field`, `UInt64`, `UInt32` and `Int64` are no longer supported https://github.com/o1-labs/o1js/pull/519 - use `Field(number | string | bigint)` and `UInt64.from(number | string | bigint)` -- Move several features out of 'experimental' https://github.com/o1-labs/snarkyjs/pull/555 +- Move several features out of 'experimental' https://github.com/o1-labs/o1js/pull/555 - `Reducer` replaces `Experimental.Reducer` - `MerkleTree` and `MerkleWitness` replace `Experimental.{MerkleTree,MerkleWitness}` - In a `SmartContract`, `this.token` replaces `this.experimental.token` ### Deprecated -- `CircuitValue` deprecated in favor of `Struct` https://github.com/o1-labs/snarkyjs/pull/416 -- Static props `Field.zero`, `Field.one`, `Field.minusOne` deprecated in favor of `Field(number)` https://github.com/o1-labs/snarkyjs/pull/524 -- `SmartContract.sign()` and `AccountUpdate.sign()` in favor of `.requireSignature()` https://github.com/o1-labs/snarkyjs/pull/558 +- `CircuitValue` deprecated in favor of `Struct` https://github.com/o1-labs/o1js/pull/416 +- Static props `Field.zero`, `Field.one`, `Field.minusOne` deprecated in favor of `Field(number)` https://github.com/o1-labs/o1js/pull/524 +- `SmartContract.sign()` and `AccountUpdate.sign()` in favor of `.requireSignature()` https://github.com/o1-labs/o1js/pull/558 ### Fixed -- Uint comparisons and division fixed inside the prover https://github.com/o1-labs/snarkyjs/pull/503 -- Callback arguments are properly passed into method invocations https://github.com/o1-labs/snarkyjs/pull/516 -- Removed internal type `JSONValue` from public interfaces https://github.com/o1-labs/snarkyjs/pull/536 -- Returning values from a zkApp https://github.com/o1-labs/snarkyjs/pull/461 +- Uint comparisons and division fixed inside the prover https://github.com/o1-labs/o1js/pull/503 +- Callback arguments are properly passed into method invocations https://github.com/o1-labs/o1js/pull/516 +- Removed internal type `JSONValue` from public interfaces https://github.com/o1-labs/o1js/pull/536 +- Returning values from a zkApp https://github.com/o1-labs/o1js/pull/461 ### Fixed -- Callback arguments are properly passed into method invocations https://github.com/o1-labs/snarkyjs/pull/516 +- Callback arguments are properly passed into method invocations https://github.com/o1-labs/o1js/pull/516 -## [0.6.1](https://github.com/o1-labs/snarkyjs/compare/ba688523...f0837188) +## [0.6.1](https://github.com/o1-labs/o1js/compare/ba688523...f0837188) ### Fixed -- Proof verification on the web version https://github.com/o1-labs/snarkyjs/pull/476 +- Proof verification on the web version https://github.com/o1-labs/o1js/pull/476 -## [0.6.0](https://github.com/o1-labs/snarkyjs/compare/f2ad423...ba688523) +## [0.6.0](https://github.com/o1-labs/o1js/compare/f2ad423...ba688523) ### Added -- `reducer.getActions` partially implemented for local testing https://github.com/o1-labs/snarkyjs/pull/327 -- `gte` and `assertGte` methods on `UInt32`, `UInt64` https://github.com/o1-labs/snarkyjs/pull/349 -- Return sent transaction `hash` for `RemoteBlockchain` https://github.com/o1-labs/snarkyjs/pull/399 +- `reducer.getActions` partially implemented for local testing https://github.com/o1-labs/o1js/pull/327 +- `gte` and `assertGte` methods on `UInt32`, `UInt64` https://github.com/o1-labs/o1js/pull/349 +- Return sent transaction `hash` for `RemoteBlockchain` https://github.com/o1-labs/o1js/pull/399 ### Changed -- BREAKING CHANGE: Rename the `Party` class to `AccountUpdate`. Also, rename other occurrences of "party" to "account update". https://github.com/o1-labs/snarkyjs/pull/393 -- BREAKING CHANGE: Don't require the account address as input to `SmartContract.compile()`, `SmartContract.digest()` and `SmartContract.analyzeMethods()` https://github.com/o1-labs/snarkyjs/pull/406 +- BREAKING CHANGE: Rename the `Party` class to `AccountUpdate`. Also, rename other occurrences of "party" to "account update". https://github.com/o1-labs/o1js/pull/393 +- BREAKING CHANGE: Don't require the account address as input to `SmartContract.compile()`, `SmartContract.digest()` and `SmartContract.analyzeMethods()` https://github.com/o1-labs/o1js/pull/406 - This works because the address / public key is now a variable in the method circuit; it used to be a constant - BREAKING CHANGE: Move `ZkProgram` to `Experimental.ZkProgram` -## [0.5.4](https://github.com/o1-labs/snarkyjs/compare/3461333...f2ad423) +## [0.5.4](https://github.com/o1-labs/o1js/compare/3461333...f2ad423) ### Fixed -- Running snarkyjs inside a web worker https://github.com/o1-labs/snarkyjs/issues/378 +- Running o1js inside a web worker https://github.com/o1-labs/o1js/issues/378 -## [0.5.3](https://github.com/o1-labs/snarkyjs/compare/4f0dd40...3461333) +## [0.5.3](https://github.com/o1-labs/o1js/compare/4f0dd40...3461333) ### Fixed -- Infinite loop when compiling in web version https://github.com/o1-labs/snarkyjs/issues/379, by [@maht0rz](https://github.com/maht0rz) +- Infinite loop when compiling in web version https://github.com/o1-labs/o1js/issues/379, by [@maht0rz](https://github.com/maht0rz) -## [0.5.2](https://github.com/o1-labs/snarkyjs/compare/55c8ea0...4f0dd40) +## [0.5.2](https://github.com/o1-labs/o1js/compare/55c8ea0...4f0dd40) ### Fixed - Crash of the web version introduced in 0.5.0 -- Issue with `Experimental.MerkleWitness` https://github.com/o1-labs/snarkyjs/pull/368 +- Issue with `Experimental.MerkleWitness` https://github.com/o1-labs/o1js/pull/368 -## [0.5.1](https://github.com/o1-labs/snarkyjs/compare/e0192f7...55c8ea0) +## [0.5.1](https://github.com/o1-labs/o1js/compare/e0192f7...55c8ea0) ### Fixed -- `fetchAccount` https://github.com/o1-labs/snarkyjs/pull/350 +- `fetchAccount` https://github.com/o1-labs/o1js/pull/350 -## [0.5.0](https://github.com/o1-labs/snarkyjs/compare/2375f08...e0192f7) +## [0.5.0](https://github.com/o1-labs/o1js/compare/2375f08...e0192f7) ### Added -- **Recursive proofs**. RFC: https://github.com/o1-labs/snarkyjs/issues/89, PRs: https://github.com/o1-labs/snarkyjs/pull/245 https://github.com/o1-labs/snarkyjs/pull/250 https://github.com/o1-labs/snarkyjs/pull/261 +- **Recursive proofs**. RFC: https://github.com/o1-labs/o1js/issues/89, PRs: https://github.com/o1-labs/o1js/pull/245 https://github.com/o1-labs/o1js/pull/250 https://github.com/o1-labs/o1js/pull/261 - Enable smart contract methods to take previous proofs as arguments, and verify them in the circuit - Add `ZkProgram`, a new primitive which represents a collection of circuits that produce instances of the same proof. So, it's a more general version of `SmartContract`, without any of the Mina-related API. `ZkProgram` is suitable for rollup-type systems and offchain usage of Pickles + Kimchi. -- **zkApp composability** -- calling other zkApps from inside zkApps. RFC: https://github.com/o1-labs/snarkyjs/issues/303, PRs: https://github.com/o1-labs/snarkyjs/pull/285, https://github.com/o1-labs/snarkyjs/pull/296, https://github.com/o1-labs/snarkyjs/pull/294, https://github.com/o1-labs/snarkyjs/pull/297 -- **Events** support via `SmartContract.events`, `this.emitEvent`. RFC: https://github.com/o1-labs/snarkyjs/issues/248, PR: https://github.com/o1-labs/snarkyjs/pull/272 - - `fetchEvents` partially implemented for local testing: https://github.com/o1-labs/snarkyjs/pull/323 -- **Payments**: `this.send({ to, amount })` as an easier API for sending Mina from smart contracts https://github.com/o1-labs/snarkyjs/pull/325 +- **zkApp composability** -- calling other zkApps from inside zkApps. RFC: https://github.com/o1-labs/o1js/issues/303, PRs: https://github.com/o1-labs/o1js/pull/285, https://github.com/o1-labs/o1js/pull/296, https://github.com/o1-labs/o1js/pull/294, https://github.com/o1-labs/o1js/pull/297 +- **Events** support via `SmartContract.events`, `this.emitEvent`. RFC: https://github.com/o1-labs/o1js/issues/248, PR: https://github.com/o1-labs/o1js/pull/272 + - `fetchEvents` partially implemented for local testing: https://github.com/o1-labs/o1js/pull/323 +- **Payments**: `this.send({ to, amount })` as an easier API for sending Mina from smart contracts https://github.com/o1-labs/o1js/pull/325 - `Party.send()` to transfer Mina between any accounts, for example, from users to smart contracts -- `SmartContract.digest()` to quickly compute a hash of the contract's circuit. This is [used by the zkApp CLI](https://github.com/o1-labs/zkapp-cli/pull/233) to figure out whether `compile` should be re-run or a cached verification key can be used. https://github.com/o1-labs/snarkyjs/pull/268 -- `Circuit.constraintSystem()` for creating a circuit from a function, counting the number of constraints and computing a digest of the circuit https://github.com/o1-labs/snarkyjs/pull/279 +- `SmartContract.digest()` to quickly compute a hash of the contract's circuit. This is [used by the zkApp CLI](https://github.com/o1-labs/zkapp-cli/pull/233) to figure out whether `compile` should be re-run or a cached verification key can be used. https://github.com/o1-labs/o1js/pull/268 +- `Circuit.constraintSystem()` for creating a circuit from a function, counting the number of constraints and computing a digest of the circuit https://github.com/o1-labs/o1js/pull/279 - `this.account.isNew` to assert that an account did not (or did) exist before the transaction https://github.com/MinaProtocol/mina/pull/11524 -- `LocalBlockchain.setTimestamp` and other setters for network state, to test network preconditions locally https://github.com/o1-labs/snarkyjs/pull/329 +- `LocalBlockchain.setTimestamp` and other setters for network state, to test network preconditions locally https://github.com/o1-labs/o1js/pull/329 - **Experimental APIs** are now collected under the `Experimental` import, or on `this.experimental` in a smart contract. -- Custom tokens (_experimental_), via `this.token`. RFC: https://github.com/o1-labs/snarkyjs/issues/233, PR: https://github.com/o1-labs/snarkyjs/pull/273, -- Actions / sequence events support (_experimental_), via `Experimental.Reducer`. RFC: https://github.com/o1-labs/snarkyjs/issues/265, PR: https://github.com/o1-labs/snarkyjs/pull/274 -- Merkle tree implementation (_experimental_) via `Experimental.MerkleTree` https://github.com/o1-labs/snarkyjs/pull/343 +- Custom tokens (_experimental_), via `this.token`. RFC: https://github.com/o1-labs/o1js/issues/233, PR: https://github.com/o1-labs/o1js/pull/273, +- Actions / sequence events support (_experimental_), via `Experimental.Reducer`. RFC: https://github.com/o1-labs/o1js/issues/265, PR: https://github.com/o1-labs/o1js/pull/274 +- Merkle tree implementation (_experimental_) via `Experimental.MerkleTree` https://github.com/o1-labs/o1js/pull/343 ### Changed -- BREAKING CHANGE: Make on-chain state consistent with other preconditions - throw an error when state is not explicitly constrained https://github.com/o1-labs/snarkyjs/pull/267 -- `CircuitValue` improvements https://github.com/o1-labs/snarkyjs/pull/269, https://github.com/o1-labs/snarkyjs/pull/306, https://github.com/o1-labs/snarkyjs/pull/341 +- BREAKING CHANGE: Make on-chain state consistent with other preconditions - throw an error when state is not explicitly constrained https://github.com/o1-labs/o1js/pull/267 +- `CircuitValue` improvements https://github.com/o1-labs/o1js/pull/269, https://github.com/o1-labs/o1js/pull/306, https://github.com/o1-labs/o1js/pull/341 - Added a base constructor, so overriding the constructor on classes that extend `CircuitValue` is now _optional_. When overriding, the base constructor can be called without arguments, as previously: `super()`. When not overriding, the expected arguments are all the `@prop`s on the class, in the order they were defined in: `new MyCircuitValue(prop1, prop2)`. - `CircuitValue.fromObject({ prop1, prop2 })` is a new, better-typed alternative for using the base constructor. - Fixed: the overridden constructor is now free to have any argument structure -- previously, arguments had to be the props in their declared order. I.e., the behaviour that's now used by the base constructor used to be forced on all constructors, which is no longer the case. - `Mina.transaction` improvements - - Support zkApp proofs when there are other account updates in the same transaction block https://github.com/o1-labs/snarkyjs/pull/280 - - Support multiple independent zkApp proofs in one transaction block https://github.com/o1-labs/snarkyjs/pull/296 -- Add previously unimplemented preconditions, like `this.network.timestamp` https://github.com/o1-labs/snarkyjs/pull/324 https://github.com/MinaProtocol/mina/pull/11577 + - Support zkApp proofs when there are other account updates in the same transaction block https://github.com/o1-labs/o1js/pull/280 + - Support multiple independent zkApp proofs in one transaction block https://github.com/o1-labs/o1js/pull/296 +- Add previously unimplemented preconditions, like `this.network.timestamp` https://github.com/o1-labs/o1js/pull/324 https://github.com/MinaProtocol/mina/pull/11577 - Improve error messages thrown from Wasm, by making Rust's `panic` log to the JS console https://github.com/MinaProtocol/mina/pull/11644 -- Not user-facing, but essential: Smart contracts fully constrain the account updates they create, inside the circuit https://github.com/o1-labs/snarkyjs/pull/278 +- Not user-facing, but essential: Smart contracts fully constrain the account updates they create, inside the circuit https://github.com/o1-labs/o1js/pull/278 ### Fixed -- Fix comparisons on `UInt32` and `UInt64` (`UInt32.lt`, `UInt32.gt`, etc) https://github.com/o1-labs/snarkyjs/issues/174, https://github.com/o1-labs/snarkyjs/issues/101. PR: https://github.com/o1-labs/snarkyjs/pull/307 +- Fix comparisons on `UInt32` and `UInt64` (`UInt32.lt`, `UInt32.gt`, etc) https://github.com/o1-labs/o1js/issues/174, https://github.com/o1-labs/o1js/issues/101. PR: https://github.com/o1-labs/o1js/pull/307 -## [0.4.3](https://github.com/o1-labs/snarkyjs/compare/e66f08d...2375f08) +## [0.4.3](https://github.com/o1-labs/o1js/compare/e66f08d...2375f08) ### Added -- Implement the [precondition RFC](https://github.com/o1-labs/snarkyjs/issues/179#issuecomment-1139413831): +- Implement the [precondition RFC](https://github.com/o1-labs/o1js/issues/179#issuecomment-1139413831): - new fields `this.account` and `this.network` on both `SmartContract` and `Party` - `this...get()` to use on-chain values in a circuit, e.g. account balance or block height - `this...{assertEqual, assertBetween, assertNothing}()` to constrain what values to allow for these -- `CircuitString`, a snark-compatible string type with methods like `.append()` https://github.com/o1-labs/snarkyjs/pull/155 +- `CircuitString`, a snark-compatible string type with methods like `.append()` https://github.com/o1-labs/o1js/pull/155 - `bool.assertTrue()`, `bool.assertFalse()` as convenient aliases for existing functionality -- `Ledger.verifyPartyProof` which can check if a proof on a transaction is valid https://github.com/o1-labs/snarkyjs/pull/208 -- Memo field in APIs like `Mina.transaction` to attach arbitrary messages https://github.com/o1-labs/snarkyjs/pull/244 +- `Ledger.verifyPartyProof` which can check if a proof on a transaction is valid https://github.com/o1-labs/o1js/pull/208 +- Memo field in APIs like `Mina.transaction` to attach arbitrary messages https://github.com/o1-labs/o1js/pull/244 - This changelog ### Changed - Huge snark performance improvements (2-10x) for most zkApps https://github.com/MinaProtocol/mina/pull/11053 - Performance improvements in node with > 4 CPUs, for all snarks https://github.com/MinaProtocol/mina/pull/11292 -- Substantial reduction of snarkyjs' size https://github.com/MinaProtocol/mina/pull/11166 +- Substantial reduction of o1js' size https://github.com/MinaProtocol/mina/pull/11166 ### Removed @@ -554,4 +876,4 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Fixed -- zkApp proving on web https://github.com/o1-labs/snarkyjs/issues/226 +- zkApp proving on web https://github.com/o1-labs/o1js/issues/226 diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..d878a07b61 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +/src/lib/gadgets @o1-labs/crypto-eng-reviewers @mitschabaude diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e39273b010..850880db1c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,25 +6,25 @@ We also welcome contributions to [zkApps Developer](https://docs.minaprotocol.co There are two ways to contribute: -1. Preferred: Maintain your own package that can be installed from [npm](https://www.npmjs.com/) and used alongside SnarkyJS. See [Creating high-quality community packages](#creating-high-quality-community-packages). -2. Directly contribute to this repo. See [Contributing to SnarkyJS](#contributing-to-snarkyjs). +1. Preferred: Maintain your own package that can be installed from [npm](https://www.npmjs.com/) and used alongside o1js. See [Creating high-quality community packages](#creating-high-quality-community-packages). +2. Directly contribute to this repo. See [Contributing to o1js](#contributing-to-o1js). If you maintain your own package, we strongly encourage you to add it to our official list of [community packages](./README.md#community-packages). -For information that is helpful for SnarkyJS core contributors, see [README-dev](README-dev.md). +For information that is helpful for o1js core contributors, see [README-dev](README-dev.md). ### Creating high-quality community packages -To ensure consistency within the SnarkyJS ecosystem and ease review and use by our team and other SnarkyJS devs, we encourage community packages to follow these standards: +To ensure consistency within the o1js ecosystem and ease review and use by our team and other o1js devs, we encourage community packages to follow these standards: - The package is published to [npm](https://www.npmjs.com/). - `npm install ` works and is all that is needed to use the package. - - SnarkyJS must be listed as a [peer dependency](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#peerdependencies). + - o1js must be listed as a [peer dependency](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#peerdependencies). - If applicable, the package must work both on the web and in NodeJS. - The package is created using the [zkApp CLI](https://github.com/o1-labs/zkapp-cli) (recommended). If you did not create the package using the zkApp CLI, follow these guidelines for code consistency: - - Use TypeScript, and export types from `d.ts` files. We suggest that you base your tsconfig on the [tsconfig.json](./tsconfig.json) that SnarkyJS uses. - - Code must be auto-formatted with [prettier](https://prettier.io/). We encourage you to use [.prettierrc.cjs](./.prettierrc.cjs), the same prettier config as SnarkyJS. + - Use TypeScript, and export types from `d.ts` files. We suggest that you base your tsconfig on the [tsconfig.json](./tsconfig.json) that o1js uses. + - Code must be auto-formatted with [prettier](https://prettier.io/). We encourage you to use [.prettierrc.cjs](./.prettierrc.cjs), the same prettier config as o1js. - The package includes tests. - If applicable, tests must demonstrate that the package's methods can successfully run as provable code. For example, when the package is used in a SmartContract or ZkProgram that is compiled and proven. - Ideally, your tests are easy to use, modify, and port to other projects by developers in the ecosystem. This is achieved by using Jest (see [jest.config.js](./jest.config.js) for an example config) or by structuring your tests as plain node scripts, like [this example](./src/lib/circuit_value.unit-test.ts). @@ -32,14 +32,14 @@ To ensure consistency within the SnarkyJS ecosystem and ease review and use by o - Include README and LICENSE files. - Comments and README must be in English and preferably use American spelling. -### Contributing to SnarkyJS +### Contributing to o1js The `main` branch contains the development version of the code. To contribute directly to this project repo, follow these steps to get your changes in the `main` branch as quickly as possible. 1. Create a new issue for your proposed changes or use an existing issue if a relevant one exists. -1. Write a request for comment (RFC) to outline your proposed changes and motivation, like this [example RFC](https://github.com/o1-labs/snarkyjs/issues/233). Describe your objective and why the change is useful, how it works, and so on. +1. Write a request for comment (RFC) to outline your proposed changes and motivation, like this [example RFC](https://github.com/o1-labs/o1js/issues/233). Describe your objective and why the change is useful, how it works, and so on. Note: If you are proposing a smaller change your RFC will be smaller, and that's ok! :) @@ -47,6 +47,6 @@ To contribute directly to this project repo, follow these steps to get your chan 1. After your RFC proposal is approved, fork the repository and implement your changes. 1. Submit a pull request and wait for code review. :) -Our goal is to include functionality within SnarkyJS when the change is likely to be widely useful for developers. For more esoteric functionality, it makes more sense to provide high-quality community packages that can be used alongside SnarkyJS. +Our goal is to include functionality within o1js when the change is likely to be widely useful for developers. For more esoteric functionality, it makes more sense to provide high-quality community packages that can be used alongside o1js. We appreciate your contribution! diff --git a/README-dev.md b/README-dev.md index c81b561d08..129a81bb9d 100644 --- a/README-dev.md +++ b/README-dev.md @@ -7,6 +7,36 @@ o1js is a TypeScript framework designed for zk-SNARKs and zkApps on the Mina blo For more information on our development process and how to contribute, see [CONTRIBUTING.md](https://github.com/o1-labs/o1js/blob/main/CONTRIBUTING.md). This document is meant to guide you through building o1js from source and understanding the development workflow. +It is a manual step to build the [o1js Reference docs](https://docs.minaprotocol.com/zkapps/o1js-reference). See [o1js Reference](https://github.com/o1-labs/docs2/wiki/o1js-Reference) in the docs wiki style guide. + +## Prerequisites + +Before starting, ensure you have the following tools installed: + +- [Git](https://git-scm.com/) +- [Node.js and npm](https://nodejs.org/) +- [Dune](https://github.com/ocaml/dune) (only needed when compiling o1js from source) +- [Cargo](https://www.rust-lang.org/learn/get-started) (only needed when compiling o1js from source) + +After cloning the repository, you need to fetch the submodules: + +```sh +git submodule update --init --recursive +``` + +## Building o1js + +For most users, building o1js is as simple as running: + +# o1js README-dev + +o1js is a TypeScript framework designed for zk-SNARKs and zkApps on the Mina blockchain. + +- [zkApps Overview](https://docs.minaprotocol.com/zkapps) +- [Mina README](/src/mina/README.md) + +For more information on our development process and how to contribute, see [CONTRIBUTING.md](https://github.com/o1-labs/o1js/blob/main/CONTRIBUTING.md). This document is meant to guide you through building o1js from source and understanding the development workflow. + ## Prerequisites Before starting, ensure you have the following tools installed: @@ -31,52 +61,52 @@ npm install npm run build ``` -This will compile the TypeScript source files, making it ready for use. The compiled OCaml and WebAssembly artifacts are version-controlled to simplify the build process for end-users. These artifacts are stored under `src/bindings/compiled`, and contain the artifacts needed for both node and web builds. These files do not have to be regenerated unless there are changes to the OCaml or Rust source files. +This command compiles the TypeScript source files, making them ready for use. The compiled OCaml and WebAssembly artifacts are version-controlled to simplify the build process for end users. These artifacts are stored under `src/bindings/compiled` and contain the artifacts needed for both node and web builds. These files only have to be regenerated if there are changes to the OCaml or Rust source files. ## Building Bindings -If you need to regenerate the OCaml and WebAssembly artifacts, you can do so within the o1js repo. The [bindings](https://github.com/o1-labs/o1js-bindings) and [Mina](https://github.com/MinaProtocol/mina) repos are both submodules of o1js, so you can build them from within the o1js repo. +To regenerate the OCaml and WebAssembly artifacts, you can do so within the o1js repo. The [bindings](https://github.com/o1-labs/o1js-bindings) and [Mina](https://github.com/MinaProtocol/mina) repos are both submodules of o1js so you can build them from within the o1js repo. -o1js depends on OCaml code that is transpiled to JavaScript using [Js_of_ocaml](https://github.com/ocsigen/js_of_ocaml), and Rust code that is transpiled to WebAssembly using [wasm-pack](https://github.com/rustwasm/wasm-pack). These artifacts allow o1js to call into [Pickles](https://github.com/MinaProtocol/mina/blob/develop/src/lib/pickles/README.md), [snarky](https://github.com/o1-labs/snarky), and [Kimchi](https://github.com/o1-labs/proof-systems) to write zk-SNARKs and zkApps. +o1js depends on OCaml code that is transpiled to JavaScript using [Js_of_ocaml](https://github.com/ocsigen/js_of_ocaml) and Rust code that is transpiled to WebAssembly using [wasm-pack](https://github.com/rustwasm/wasm-pack). These artifacts allow o1js to call into [Pickles](https://github.com/MinaProtocol/mina/blob/develop/src/lib/pickles/README.md), [snarky](https://github.com/o1-labs/snarky), and [Kimchi](https://github.com/o1-labs/proof-systems) to write zk-SNARKs and zkApps. -The compiled artifacts are stored under `src/bindings/compiled`, and are version-controlled to simplify the build process for end-users. +The compiled artifacts are stored under `src/bindings/compiled` and are version-controlled to simplify the build process for end-users. -If you wish to rebuild the OCaml and Rust artifacts, you must be able to build the Mina repo before building the bindings. See the [Mina Dev Readme](https://github.com/MinaProtocol/mina/blob/develop/README-dev.md) for more information. Once you have configured your environment to build Mina, you can build the bindings: +If you want to rebuild the OCaml and Rust artifacts, you must be able to build the mina repo before building the bindings. See the [Mina Dev Readme](https://github.com/MinaProtocol/mina/blob/develop/README-dev.md) for more information. After you have configured your environment to build mina, you can build the bindings: ```sh npm run build:update-bindings ``` -This will build the OCaml and Rust artifacts, and copy them to the `src/bindings/compiled` directory. +This command builds the OCaml and Rust artifacts and copies them to the `src/bindings/compiled` directory. ### Build Scripts -The root build script which kicks off the build process is under `src/bindings/scripts/update-snarkyjs-bindings.sh`. This script is responsible for building the Node.js and web artifacts for o1js, and places them under `src/bindings/compiled`, to be used by o1js. +The root build script which kicks off the build process is under `src/bindings/scripts/update-o1js-bindings.sh`. This script is responsible for building the Node.js and web artifacts for o1js, and places them under `src/bindings/compiled`, to be used by o1js. ### OCaml Bindings o1js depends on Pickles, snarky, and parts of the Mina transaction logic, all of which are compiled to JavaScript and stored as artifacts to be used by o1js natively. The OCaml bindings are located under `src/bindings`. See the [OCaml Bindings README](https://github.com/o1-labs/o1js-bindings/blob/main/README.md) for more information. -To compile the OCaml code, a build tool called Dune is used. Dune is a build system for OCaml projects, and is used in addition with Js_of_ocaml to compile the OCaml code to JavaScript. The dune file that is responsible for compiling the OCaml code is located under `src/bindings/ocaml/dune`. There are two build targets: `snarky_js_node` and `snarky_js_web`, which compile the Mina dependencies as well as link the wasm artifacts to build the Node.js and web artifacts, respectively. The output file is `snark_js_node.bc.js`, which is used by o1js. +To compile the OCaml code, a build tool called Dune is used. Dune is a build system for OCaml projects, and is used in addition with Js_of_ocaml to compile the OCaml code to JavaScript. The dune file that is responsible for compiling the OCaml code is located under `src/bindings/ocaml/dune`. There are two build targets: `o1js_node` and `o1js_web`, which compile the Mina dependencies as well as link the wasm artifacts to build the Node.js and web artifacts, respectively. The output file is `o1js_node.bc.js`, which is used by o1js. ### WebAssembly Bindings -o1js additionally depends on Kimchi, which is compiled to WebAssembly. Kimchi is located in the Mina repo, under `src/mina`. See the [Kimchi README](https://github.com/o1-labs/proof-systems/blob/master/README.md) for more information. +o1js additionally depends on Kimchi, which is compiled to WebAssembly. Kimchi is located in the Mina repo under `src/mina`. See the [Kimchi README](https://github.com/o1-labs/proof-systems/blob/master/README.md) for more information. -To compile the wasm code, a combination of Cargo and Dune is used. Both build files are located under `src/mina/src/lib/crypto/kimchi`, where the `wasm` folder contains the Rust code which is compiled to wasm, and the `js` folder which contains a wrapper around the wasm code which allows Js_of_ocaml to compile against the wasm backend. +To compile the Wasm code, a combination of Cargo and Dune is used. Both build files are located under `src/mina/src/lib/crypto/kimchi`, where the `wasm` folder contains the Rust code that is compiled to Wasm, and the `js` folder that contains a wrapper around the Wasm code which allows Js_of_ocaml to compile against the Wasm backend. -For the wasm build, the output files are: +For the Wasm build, the output files are: - `plonk_wasm_bg.wasm`: The compiled WebAssembly binary. - `plonk_wasm_bg.wasm.d.ts`: TypeScript definition files describing the types of .wasm or .js files. -- `plonk_wasm.js`: JavaScript file that wraps the WASM code for use in Node.js. +- `plonk_wasm.js`: JavaScript file that wraps the Wasm code for use in Node.js. - `plonk_wasm.d.ts`: TypeScript definition file for plonk_wasm.js. ### Generated Constant Types -In addition to building the OCaml and Rust code, the build script also generates TypeScript types for constants used in the Mina protocol. These types are generated from the OCaml source files, and are located under `src/bindings/crypto/constants.ts` and `src/bindings/mina-transaction/gen`. When building the bindings, these constants are auto-generated by Dune. If you wish to add a new constant, you can edit the `src/bindings/ocaml/snarky_js_constants` file, and then run `npm run build:bindings` to regenerate the TypeScript files. +In addition to building the OCaml and Rust code, the build script also generates TypeScript types for constants used in the Mina protocol. These types are generated from the OCaml source files, and are located under `src/bindings/crypto/constants.ts` and `src/bindings/mina-transaction/gen`. When building the bindings, these constants are auto-generated by Dune. If you wish to add a new constant, you can edit the `src/bindings/ocaml/o1js_constants` file, and then run `npm run build:bindings` to regenerate the TypeScript files. -These types are used by o1js to ensure that the constants used in the protocol are consistent with the OCaml source files. +o1js uses these types to ensure that the constants used in the protocol are consistent with the OCaml source files. ## Development @@ -88,7 +118,7 @@ If you work on o1js, create a feature branch off of one of these base branches. **Default to `main` as the base branch**. -The other base branches (`berkeley`, `develop`) are only used in specific scenarios where you want to adapt o1js to changes in the sibling repos on those other branches. Even then, consider whether it is feasible to land your changes to `main` and merge to `berkeley` and `develop` afterwards. Only changes in `main` will ever be released, so anything in the other branches has to be backported and reconciled with `main` eventually. +The other base branches (`berkeley` and `develop`) are used only in specific scenarios where you want to adapt o1js to changes in the sibling repos on those other branches. Even then, consider whether it is feasible to land your changes to `main` and merge to `berkeley` and `develop` afterwards. Only changes in `main` will ever be released, so anything in the other branches has to be backported and reconciled with `main` eventually. | Repository | mina -> o1js -> o1js-bindings | | ---------- | -------------------------------- | @@ -96,13 +126,13 @@ The other base branches (`berkeley`, `develop`) are only used in specific scenar | | berkeley -> berkeley -> berkeley | | | develop -> develop -> develop | -- `o1js-main`: The o1js-main branch in the Mina repository corresponds to the main branch in both o1js and o1js-bindings repositories. This is where stable releases and ramp-up features are maintained. The o1js-main branch runs in parallel to the Mina `berkeley` branch and does not have a subset or superset relationship with it. The branching structure is as follows (<- means direction to merge): +- `o1js-main`: The `o1js-main` branch in the Mina repository corresponds to the `main` branch in both o1js and o1js-bindings repositories. This branch is where stable releases and ramp-up features are maintained. The `o1js-main` branch runs in parallel to the Mina `berkeley` branch and does not have a subset or superset relationship with it. The branching structure is as follows (<- means direction to merge): - - `develop` <- `o1js-main` <- `current testnet` - Typically, the current testnet often corresponds to the rampup branch. + - `develop` <- `o1js-main` <- `current testnet` - Typically, the current Testnet often corresponds to the rampup branch. -- `berkeley`: The berkeley branch is maintained across all three repositories. This branch is used for features and updates specific to the Berkeley release of the project. +- `berkeley`: The `berkeley` branch is maintained across all three repositories. This branch is used for features and updates specific to the Berkeley release of the project. -- `develop`: The develop branch is also maintained across all three repositories. It is used for ongoing development, testing new features, and integration work. +- `develop`: The `develop` branch is also maintained across all three repositories. It is used for ongoing development, testing new features, and integration work. ### Running Tests @@ -113,15 +143,29 @@ npm run test npm run test:unit ``` -This will run all the unit tests and provide you with a summary of the test results. +In order for the mina-signer tests to run you must also build from inside its subdirectory: + +```sh +cd src/mina-signer +npm run build +cd ../.. +``` + +This runs all the unit tests and provides you with a summary of the test results. + +Note that you can run individual jest tests via the command: + +```sh +./jest +``` -You can additionally run integration tests by running: +You can also run integration tests by running: ```sh npm run test:integration ``` -Finally, we have a set of end-to-end tests that run against the browser. These tests are not run by default, but you can run them by running: +Finally, a set of end-to-end tests are run against the browser. These tests are not run by default, but you can run them by running: ```sh npm install @@ -137,7 +181,7 @@ npm run e2e:show-report -You can execute the CI locally by using [act](https://github.com/nektos/act). First generate a GitHub token and use: +You can execute the CI locally by using [act](https://github.com/nektos/act). First, generate a GitHub token and use: ```sh act -j Build-And-Test-Server --matrix test_type:"Simple integration tests" -s $GITHUB_TOKEN @@ -145,42 +189,59 @@ act -j Build-And-Test-Server --matrix test_type:"Simple integration tests" -s $G ### Releasing -To release a new version of o1js, you must first update the version number in `package.json`. Then, you can create a new pull request to merge your changes into the main branch. Once the pull request is merged, a CI job will automatically publish the new version to npm. +To release a new version of o1js, you must first update the version number in `package.json`. Then, you can create a new pull request to merge your changes into the main branch. After the pull request is merged, a CI job automatically publishes the new version to npm. + +## Testing and Debugging + +### Test zkApps against Lightnet network + +Use the lightweight Mina blockchain network (Lightnet) to test on a local blockchain before you test with a live network. To test zkApps against the local blockchain, first spin up Lightnet. + +The easiest way is to use [zkApp CLI](https://www.npmjs.com/package/zkapp-cli) sub-commands: -## Test zkApps against the local blockchain network +```shell +zk lightnet start # start the local network +# Do your tests and other interactions with the network +zk lightnet logs # manage the logs of the local network +zk lightnet explorer # visualize the local network state +zk lightnet stop # stop the local network +``` + +Use `zk lightnet --help` for more information. + +You can also use the corresponding [Docker image](https://hub.docker.com/r/o1labs/mina-local-network) manually: + +```shell +docker run --rm --pull=missing -it \ + --env NETWORK_TYPE="single-node" \ + --env PROOF_LEVEL="none" \ + --env LOG_LEVEL="Trace" \ + -p 3085:3085 \ + -p 5432:5432 \ + -p 8080:8080 \ + -p 8181:8181 \ + -p 8282:8282 \ + o1labs/mina-local-network:o1js-main-latest-lightnet +``` -In order to be able to test zkApps against the local blockchain network, you need to spin up such a network first. -You can do so in several ways. +See the [Docker Hub repository](https://hub.docker.com/r/o1labs/mina-local-network) for more information. -1. Using [zkapp-cli](https://www.npmjs.com/package/zkapp-cli)'s sub commands: +Next up, get the Mina blockchain accounts information to be used in your zkApp. +After the local network is up and running, you can use the [Lightnet](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/lib/fetch.ts#L1012) `o1js API namespace` to get the accounts information. +See the corresponding example in [src/examples/zkapps/hello-world/run-live.ts](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/examples/zkapps/hello-world/run-live.ts). - ```shell - zk lightnet start # start the local network - # Do your tests and other interactions with the network - zk lightnet logs # manage the logs of the local network - zk lightnet explorer # visualize the local network state - zk lightnet stop # stop the local network - ``` +### Profiling o1js - Please refer to `zk lightnet --help` for more information. +To enhance the development experience and optimize the performance of o1js, use the Chrome Debugger alongside Node.js. This setup is particularly useful when you want to profile the performance of your zkApp or o1js. -2. Using the corresponding [Docker image](https://hub.docker.com/r/o1labs/mina-local-network) manually: +#### Using the `run-debug` script - ```shell - docker run --rm --pull=missing -it \ - --env NETWORK_TYPE="single-node" \ - --env PROOF_LEVEL="none" \ - --env LOG_LEVEL="Trace" \ - -p 3085:3085 \ - -p 5432:5432 \ - -p 8080:8080 \ - -p 8181:8181 \ - -p 8282:8282 \ - o1labs/mina-local-network:o1js-main-latest-lightnet - ``` +To facilitate this process, use the provided script named `run-debug`. To use this script, run: + +```sh +./run-debug --bundle +``` - Please refer to the [Docker Hub repository](https://hub.docker.com/r/o1labs/mina-local-network) for more information. +This script initializes a Node.js process with the `--inspect-brk` flag that starts the Node.js inspector and breaks before the user script starts (i.e., it pauses execution until a debugger is attached). The `--enable-source-maps` flag ensures that source maps are used to allow easy debugging of o1js code directly. -Next up, you will need the Mina blockchain accounts information in order to be used in your zkApp. -Once the local network is up and running, you can use the [Lightnet](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/lib/fetch.ts#L1012) `o1js API namespace` to get the accounts information. -The corresponding example can be found here: [src/examples/zkapps/hello_world/run_live.ts](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/examples/zkapps/hello_world/run_live.ts) +After the Node.js process is running, open the Chrome browser and navigate to `chrome://inspect` to attach the Chrome Debugger to the Node.js process. You can set breakpoints, inspect variables, and profile the performance of your zkApp or o1js. For more information on using the Chrome Debugger, see the [DevTools documentation](https://developer.chrome.com/docs/devtools/). diff --git a/README.md b/README.md index 8745860a82..637a852691 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,32 @@ -# SnarkyJS   [![npm version](https://img.shields.io/npm/v/snarkyjs.svg?style=flat)](https://www.npmjs.com/package/snarkyjs) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/o1-labs/snarkyjs/blob/main/CONTRIBUTING.md) +# o1js   [![npm version](https://img.shields.io/npm/v/o1js.svg?style=flat)](https://www.npmjs.com/package/o1js) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/o1-labs/o1js/blob/main/CONTRIBUTING.md) -SnarkyJS helps developers build apps powered by zero-knowledge (zk) cryptography. +ℹ️ **o1js** is an evolution of [SnarkyJS](https://www.npmjs.com/package/snarkyjs) which saw +49 updated versions over two years of development with 43,141 downloads. -The easiest way to write zk programs is using SnarkyJS. +This name change to o1js reflects the evolution of our vision for the premiere toolkit used by developers to build zero knowledge-enabled applications, while paying homage to our technology's recursive proof generation capabilities. -SnarkyJS is a TypeScript library for [zk-SNARKs](https://minaprotocol.com/blog/what-are-zk-snarks) and zkApps. You can use SnarkyJS to write zk smart contracts based on zero-knowledge proofs for the Mina Protocol. +Your favorite functionality stays the same and transitioning to o1js is a quick and easy process: -SnarkyJS is automatically included when you create a project using the [Mina zkApp CLI](https://github.com/o1-labs/zkapp-cli). +- To update zkApp-cli, run the following command: + + `npm i -g zkapp-cli@latest` + +- To remove the now-deprecated SnarkyJS package and install o1js, run the following command: + + `npm remove snarkyjs && npm install o1js` + +- For existing zkApps, make sure to update your imports from `snarkyjs` to `o1js` +- No need to redeploy, you are good to go! + +## o1js + +o1js helps developers build apps powered by zero knowledge (zk) cryptography. + +The easiest way to write zk programs is using o1js. + +o1js is a TypeScript library for [zk-SNARKs](https://minaprotocol.com/blog/what-are-zk-snarks) and zkApps. You can use o1js to write zk smart contracts based on zero-knowledge proofs for the Mina Protocol. + +o1js is automatically included when you create a project using the [Mina zkApp CLI](https://github.com/o1-labs/zkapp-cli). ## Learn More @@ -14,22 +34,27 @@ SnarkyJS is automatically included when you create a project using the [Mina zkA - For guided steps building and using zkApps, see the [zkApp Developers Tutorials](https://docs.minaprotocol.com/zkapps/tutorials/hello-world). -- To meet other developers building zkApps with SnarkyJS, participate in the [#zkapps-developers](https://discord.com/channels/484437221055922177/915745847692636181) channel on Mina Protocol Discord. +- To meet other developers building zkApps with o1js, participate in the [#zkapps-developers](https://discord.com/channels/484437221055922177/915745847692636181) channel on Mina Protocol Discord. -- For a list of changes between versions, see the [CHANGELOG](https://github.com/o1-labs/snarkyjs/blob/main/CHANGELOG.md). +- For a list of changes between versions, see the [CHANGELOG](https://github.com/o1-labs/o1js/blob/main/CHANGELOG.md). -- To stay up to date with SnarkyJS, see the [O(1) Labs Blog](https://blog.o1labs.org/tagged/snarkyjs). +- To stay up to date with o1js, see the [O(1) Labs Blog](https://blog.o1labs.org/tagged/o1js). ## Contributing -SnarkyJS is an open source project. We appreciate all community contributions to SnarkyJS! +o1js is an open source project. We appreciate all community contributions to o1js! + +See the [Contributing guidelines](https://github.com/o1-labs/o1js/blob/main/CONTRIBUTING.md) for ways you can contribute. + +## Development Workflow -See the [Contributing guidelines](https://github.com/o1-labs/snarkyjs/blob/main/CONTRIBUTING.md) for ways you can contribute. +For guidance on building o1js from source and understanding the development workflow, see [o1js README-dev](https://github.com/o1-labs/o1js/blob/main/README-dev.md). ## Community Packages -High-quality community packages from open source developers are available for your project. +High-quality community packages from open source developers are available for your project. -- **snarkyjs-elgamal** A partially homomorphic encryption library for SnarkyJS based on Elgamal encryption: [GitHub](https://github.com/Trivo25/snarkyjs-elgamal) and [npm](https://www.npmjs.com/package/snarkyjs-elgamal) +- **o1js-elgamal** A partially homomorphic encryption library for o1js based on Elgamal encryption: [GitHub](https://github.com/Trivo25/o1js-elgamal) and [npm](https://www.npmjs.com/package/o1js-elgamal) +- **o1js-pack** A library for o1js that allows a zkApp developer to pack extra data into a single Field. [GitHub](https://github.com/45930/o1js-pack) and [npm](https://www.npmjs.com/package/o1js-pack) -To include your package, see [Creating high-quality community packages](https://github.com/o1-labs/snarkyjs/blob/main/CONTRIBUTING.md#creating-high-quality-community-packages). +To include your package, see [Creating high-quality community packages](https://github.com/o1-labs/o1js/blob/main/CONTRIBUTING.md#creating-high-quality-community-packages). diff --git a/benchmark/benchmark.ts b/benchmark/benchmark.ts new file mode 100644 index 0000000000..c091fd64c6 --- /dev/null +++ b/benchmark/benchmark.ts @@ -0,0 +1,172 @@ +/** + * Base benchmark harness + */ + +import jStat from 'jstat'; +export { + Benchmark, + BenchmarkResult, + benchmark, + calculateBounds, + logResult, + pValue, +}; + +type BenchmarkResult = { + label: string; + size: number; + mean: number; + variance: number; +}; + +type Benchmark = { run: () => Promise }; + +function benchmark( + label: string, + run: + | (( + tic: (label?: string) => void, + toc: (label?: string) => void + ) => Promise) + | ((tic: (label?: string) => void, toc: (label?: string) => void) => void), + options?: { + numberOfRuns?: number; + numberOfWarmups?: number; + } +): Benchmark { + return { + async run() { + const { numberOfRuns = 5, numberOfWarmups = 0 } = options ?? {}; + + let lastStartKey: string; + let startTime: Record = {}; // key: startTime + let runTimes: Record = {}; // key: [(endTime - startTime)] + + function reset() { + startTime = {}; + } + + function start(key?: string) { + lastStartKey = key ?? ''; + key = getKey(label, key); + if (startTime[key] !== undefined) + throw Error('running `start(label)` with an already started label'); + startTime[key] = performance.now(); + } + + function stop(key?: string) { + let end = performance.now(); + key ??= lastStartKey; + if (key === undefined) { + throw Error('running `stop()` with no start defined'); + } + key = getKey(label, key); + let start_ = startTime[key]; + startTime[key] = undefined; + if (start_ === undefined) + throw Error('running `stop()` with no start defined'); + let times = (runTimes[key] ??= []); + times.push(end - start_); + } + + let noop = () => {}; + for (let i = 0; i < numberOfWarmups; i++) { + reset(); + await run(noop, noop); + } + for (let i = 0; i < numberOfRuns; i++) { + reset(); + await run(start, stop); + } + + const results: BenchmarkResult[] = []; + + for (let label in runTimes) { + let times = runTimes[label]; + results.push({ label, ...getStatistics(times) }); + } + return results; + }, + }; +} + +function getKey(label: string, key?: string) { + return key ? `${label} - ${key}` : label; +} + +function getStatistics(numbers: number[]) { + let sum = 0; + let sumSquares = 0; + for (let i of numbers) { + sum += i; + sumSquares += i ** 2; + } + let n = numbers.length; + let mean = sum / n; + let variance = (sumSquares - sum ** 2 / n) / (n - 1); + + return { mean, variance, size: n }; +} + +function logResult( + result: BenchmarkResult, + previousResult?: BenchmarkResult +): void { + console.log(result.label); + console.log(`time: ${resultToString(result)}`); + + if (previousResult === undefined) return; + + let change = (result.mean - previousResult.mean) / previousResult.mean; + let p = pValue(result, previousResult); + + let changePositive = change > 0 ? '+' : ''; + let pGreater = p > 0.05 ? '>' : '<'; + console.log( + `change: ${changePositive}${(change * 100).toFixed(3)}% (p = ${p.toFixed( + 2 + )} ${pGreater} 0.05)` + ); + + if (p < 0.05) { + if (result.mean < previousResult.mean) { + console.log('Performance has improved.'); + } else { + console.log('Performance has regressed.'); + } + } else { + console.log('Change within noise threshold.'); + } +} + +function resultToString({ mean, variance }: BenchmarkResult) { + return `${mean.toFixed(3)}ms ± ${((Math.sqrt(variance) / mean) * 100).toFixed( + 1 + )}%`; +} + +function pValue(sample1: BenchmarkResult, sample2: BenchmarkResult): number { + const n1 = sample1.size; + const n2 = sample2.size; + const mean1 = sample1.mean; + const mean2 = sample2.mean; + const var1 = sample1.variance / n1; + const var2 = sample2.variance / n2; + + // calculate the t-statistic + const tStatistic = (mean1 - mean2) / Math.sqrt(var1 + var2); + + // degrees of freedom + const df = (var1 + var2) ** 2 / (var1 ** 2 / (n1 - 1) + var2 ** 2 / (n2 - 1)); + + // calculate the (two-sided) p-value indicating a significant change + const pValue = 2 * (1 - jStat.studentt.cdf(Math.abs(tStatistic), df)); + return pValue; +} + +function calculateBounds(result: BenchmarkResult) { + const stdDev = Math.sqrt(result.variance); + const upperBound = result.mean + stdDev; + const lowerBound = result.mean - stdDev; + return { upperBound, lowerBound }; +} diff --git a/benchmark/benchmarks/ecdsa.ts b/benchmark/benchmarks/ecdsa.ts new file mode 100644 index 0000000000..28de1c4544 --- /dev/null +++ b/benchmark/benchmarks/ecdsa.ts @@ -0,0 +1,43 @@ +/** + * ECDSA benchmark + */ + +import { Provable } from 'o1js'; +import { + Bytes32, + Ecdsa, + Secp256k1, + keccakAndEcdsa, +} from '../../src/examples/crypto/ecdsa/ecdsa.js'; +import { benchmark } from '../benchmark.js'; + +let privateKey = Secp256k1.Scalar.random(); +let publicKey = Secp256k1.generator.scale(privateKey); +let message = Bytes32.fromString("what's up"); +let signature = Ecdsa.sign(message.toBytes(), privateKey.toBigInt()); + +const EcdsaBenchmark = benchmark( + 'ecdsa', + async (tic, toc) => { + tic('build constraint system'); + await keccakAndEcdsa.analyzeMethods(); + toc(); + + tic('witness generation'); + await Provable.runAndCheck(async () => { + let message_ = Provable.witness(Bytes32.provable, () => message); + let signature_ = Provable.witness(Ecdsa.provable, () => signature); + let publicKey_ = Provable.witness(Secp256k1.provable, () => publicKey); + await keccakAndEcdsa.rawMethods.verifyEcdsa( + message_, + signature_, + publicKey_ + ); + }); + toc(); + }, + // two warmups to ensure full caching + { numberOfWarmups: 2, numberOfRuns: 5 } +); + +export default EcdsaBenchmark; diff --git a/benchmark/runners/simple.ts b/benchmark/runners/simple.ts new file mode 100644 index 0000000000..4d447a4c81 --- /dev/null +++ b/benchmark/runners/simple.ts @@ -0,0 +1,21 @@ +/** + * Simple benchmarks runner + * Exercises benchmarks and logs the results + * + * Run with + * ``` + * ./run benchmark/runners/simple.ts --bundle + * ``` + */ + +import { logResult } from '../benchmark.js'; +import EcdsaBenchmark from '../benchmarks/ecdsa.js'; + +// Run all benchmarks +const results = [...(await EcdsaBenchmark.run())]; + +// Process and log results +for (const result of results) { + logResult(result); + console.log('\n'); +} diff --git a/benchmark/runners/with-cloud-history.ts b/benchmark/runners/with-cloud-history.ts new file mode 100644 index 0000000000..463a3c61b3 --- /dev/null +++ b/benchmark/runners/with-cloud-history.ts @@ -0,0 +1,26 @@ +/** + * Benchmarks runner with historical data preservation in cloud storage (InfluxDB) + * + * Run with + * ``` + * ./run benchmark/runners/with-cloud-history.ts --bundle + * ``` + */ + +import { logResult } from '../benchmark.js'; +import EcdsaBenchmark from '../benchmarks/ecdsa.js'; +import { + readPreviousResultFromInfluxDb, + writeResultToInfluxDb, +} from '../utils/influxdb-utils.js'; + +// Run all benchmarks +const results = [...(await EcdsaBenchmark.run())]; + +// Process and log results +for (const result of results) { + const previousResult = await readPreviousResultFromInfluxDb(result); + logResult(result, previousResult); + writeResultToInfluxDb(result); + console.log('\n'); +} diff --git a/benchmark/tsconfig.json b/benchmark/tsconfig.json new file mode 100644 index 0000000000..98fbc7dcbe --- /dev/null +++ b/benchmark/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "include": ["."], + "exclude": [], + "compilerOptions": { + "noEmit": true, // no build output, we just want type checking + "rootDir": "..", + "baseUrl": "..", + "paths": { + "o1js": ["."] + } + } +} diff --git a/benchmark/types.d.ts b/benchmark/types.d.ts new file mode 100644 index 0000000000..3ad10a0219 --- /dev/null +++ b/benchmark/types.d.ts @@ -0,0 +1 @@ +declare module 'jstat'; diff --git a/benchmark/utils/influxdb-utils.ts b/benchmark/utils/influxdb-utils.ts new file mode 100644 index 0000000000..089d39a2dd --- /dev/null +++ b/benchmark/utils/influxdb-utils.ts @@ -0,0 +1,132 @@ +/** + * InfluxDB utils + */ + +import { InfluxDB, Point } from '@influxdata/influxdb-client'; +import os from 'node:os'; +import { BenchmarkResult, calculateBounds } from '../benchmark.js'; + +const INFLUXDB_CLIENT_OPTIONS = { + url: process.env.INFLUXDB_URL, + token: process.env.INFLUXDB_TOKEN, + org: process.env.INFLUXDB_ORG, + bucket: process.env.INFLUXDB_BUCKET, +}; +const INFLUXDB_COMMON_POINT_TAGS = { + sourceEnvironment: process.env.METRICS_SOURCE_ENVIRONMENT ?? 'local', + operatingSystem: `${os.type()} ${os.release()} ${os.arch()}`, + hardware: `${os.cpus()[0].model}, ${os.cpus().length} cores, ${( + os.totalmem() / Math.pow(1024, 3) + ).toFixed(2)}Gb of RAM`, + gitBranch: process.env.GIT_BRANCH ?? 'unknown', +}; +const influxDbClient = setupInfluxDbClient(); + +function setupInfluxDbClient(): InfluxDB | undefined { + const { url, token } = INFLUXDB_CLIENT_OPTIONS; + if (url === undefined || token === undefined) { + return undefined; + } + return new InfluxDB({ url, token }); +} + +export function writeResultToInfluxDb(result: BenchmarkResult): void { + const { org, bucket } = INFLUXDB_CLIENT_OPTIONS; + if (influxDbClient && org && bucket) { + console.log('Writing result to InfluxDB.'); + const influxDbWriteClient = influxDbClient.getWriteApi(org, bucket, 'ms'); + try { + const sampleName = result.label.split('-')[1].trim(); + const { upperBound, lowerBound } = calculateBounds(result); + const point = new Point(`${result.label} - ${result.size} samples`) + .tag('benchmarkName', result.label.trim()) + .tag('sampledTimes', result.size.toString()) + .floatField('mean', result.mean) + .floatField('variance', result.variance) + .floatField(`${sampleName} - upperBound`, upperBound) + .floatField(`${sampleName} - lowerBound`, lowerBound) + .intField('size', result.size); + for (const [key, value] of Object.entries(INFLUXDB_COMMON_POINT_TAGS)) { + point.tag(key, value.trim()); + } + influxDbWriteClient.writePoint(point); + } catch (e) { + console.error('Error writing to InfluxDB: ', e); + } finally { + influxDbWriteClient.close(); + } + } else { + console.info('Skipping writing to InfluxDB: client is not configured.'); + } +} + +export function readPreviousResultFromInfluxDb( + result: BenchmarkResult +): Promise { + return new Promise((resolve) => { + const { org, bucket } = INFLUXDB_CLIENT_OPTIONS; + if (!influxDbClient || !org || !bucket) { + resolve(undefined); + return; + } + console.log('Querying InfluxDB for previous results.'); + const influxDbPointTags = INFLUXDB_COMMON_POINT_TAGS; + const influxDbQueryClient = influxDbClient.getQueryApi(org); + const baseBranchForComparison = + process.env.METRICS_BASE_BRANCH_FOR_COMPARISON ?? 'main'; + const fluxQuery = ` + from(bucket: "${bucket}") + |> range(start: -90d) + |> filter(fn: (r) => r.benchmarkName == "${result.label}") + |> filter(fn: (r) => r.gitBranch == "${baseBranchForComparison}") + |> filter(fn: (r) => r.sampledTimes == "${result.size}") + |> filter(fn: (r) => r.sourceEnvironment == "${influxDbPointTags.sourceEnvironment}") + |> filter(fn: (r) => r.operatingSystem == "${influxDbPointTags.operatingSystem}") + |> filter(fn: (r) => r.hardware == "${influxDbPointTags.hardware}") + |> toFloat() + |> group() + |> pivot( + rowKey:["_measurement"], + columnKey: ["_field"], + valueColumn: "_value" + ) + |> sort(desc: true) + |> limit(n:1) + `; + try { + let previousResult: BenchmarkResult | undefined = undefined; + influxDbQueryClient.queryRows(fluxQuery, { + next(row, tableMeta) { + const tableObject = tableMeta.toObject(row); + if ( + !previousResult && + tableObject._measurement && + tableObject.mean && + tableObject.variance && + tableObject.size + ) { + const measurement = tableObject._measurement; + previousResult = { + label: measurement + .substring(0, measurement.lastIndexOf('-')) + .trim(), + mean: parseFloat(tableObject.mean), + variance: parseFloat(tableObject.variance), + size: parseInt(tableObject.size, 10), + }; + } + }, + error(e) { + console.error('Error querying InfluxDB: ', e); + resolve(undefined); + }, + complete() { + resolve(previousResult); + }, + }); + } catch (e) { + console.error('Error querying InfluxDB: ', e); + resolve(undefined); + } + }); +} diff --git a/generate-keys.js b/generate-keys.js index ef1a4d7b9b..b702948882 100755 --- a/generate-keys.js +++ b/generate-keys.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -import Client from './dist/node/mina-signer/MinaSigner.js'; +import Client from './dist/node/mina-signer/mina-signer.js'; let client = new Client({ network: 'testnet' }); diff --git a/package-lock.json b/package-lock.json index 1d3f046ac1..e94b7fdc43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { - "name": "snarkyjs", - "version": "0.12.1", - "lockfileVersion": 2, + "name": "o1js", + "version": "0.17.0", + "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "snarkyjs", - "version": "0.12.1", + "name": "o1js", + "version": "0.17.0", "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", - "detect-gpu": "^5.0.5", + "cachedir": "^2.4.0", "isomorphic-fetch": "^3.0.0", "js-sha256": "^0.9.0", "reflect-metadata": "^0.1.13", @@ -20,39 +20,51 @@ "snarky-run": "src/build/run.js" }, "devDependencies": { + "@influxdata/influxdb-client": "^1.33.2", + "@noble/hashes": "^1.3.2", "@playwright/test": "^1.25.2", "@types/isomorphic-fetch": "^0.0.36", "@types/jest": "^27.0.0", "@types/node": "^18.14.2", "@typescript-eslint/eslint-plugin": "^5.0.0", - "esbuild": "^0.16.16", + "esbuild": "^0.19.2", "eslint": "^8.0.0", "expect": "^29.0.1", "fs-extra": "^10.0.0", "glob": "^8.0.3", "howslow": "^0.1.0", "jest": "^28.1.3", + "jstat": "^1.9.6", "minimist": "^1.2.7", "prettier": "^2.8.4", "replace-in-file": "^6.3.5", "rimraf": "^3.0.2", "ts-jest": "^28.0.8", - "typedoc": "^0.24.6", + "typedoc": "^0.24.8", "typedoc-plugin-markdown": "^3.15.3", "typedoc-plugin-merge-modules": "^5.0.1", - "typescript": "^4.9.5" + "typescript": "5.1" }, "engines": { "node": ">=16.4.0" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { @@ -60,47 +72,119 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.13.tgz", - "integrity": "sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", + "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz", - "integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helpers": "^7.18.9", - "@babel/parser": "^7.18.13", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.13", - "@babel/types": "^7.18.13", - "convert-source-map": "^1.7.0", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", + "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.3", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.3", + "@babel/types": "^7.23.3", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -110,209 +194,289 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.13.tgz", - "integrity": "sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", + "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", "dev": true, "dependencies": { - "@babel/types": "^7.18.13", + "@babel/types": "^7.23.3", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=6.0.0" + "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz", - "integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "dependencies": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.20.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "bin": { + "semver": "bin/semver.js" } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", - "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.18.6", - "@babel/types": "^7.18.9" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", - "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", - "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dev": true, "dependencies": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/parser": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz", - "integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", + "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -469,12 +633,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -484,33 +648,33 @@ } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.13.tgz", - "integrity": "sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.18.13", - "@babel/types": "^7.18.13", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", + "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.3", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.3", + "@babel/types": "^7.23.3", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -518,14 +682,23 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/types": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz", - "integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", + "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -538,432 +711,115 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@esbuild/android-arm": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.16.tgz", - "integrity": "sha512-BUuWMlt4WSXod1HSl7aGK8fJOsi+Tab/M0IDK1V1/GstzoOpqc/v3DqmN8MkuapPKQ9Br1WtLAN4uEgWR8x64A==", + "node_modules/@esbuild/linux-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz", + "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==", "cpu": [ - "arm" + "x64" ], "dev": true, "optional": true, "os": [ - "android" + "linux" ], "engines": { "node": ">=12" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.16.tgz", - "integrity": "sha512-hFHVAzUKp9Tf8psGq+bDVv+6hTy1bAOoV/jJMUWwhUnIHsh6WbFMhw0ZTkqDuh7TdpffFoHOiIOIxmHc7oYRBQ==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.16.tgz", - "integrity": "sha512-9WhxJpeb6XumlfivldxqmkJepEcELekmSw3NkGrs+Edq6sS5KRxtUBQuKYDD7KqP59dDkxVbaoPIQFKWQG0KLg==", - "cpu": [ - "x64" - ], + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true, - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=12" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.16.tgz", - "integrity": "sha512-8Z+wld+vr/prHPi2O0X7o1zQOfMbXWGAw9hT0jEyU/l/Yrg+0Z3FO9pjPho72dVkZs4ewZk0bDOFLdZHm8jEfw==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint/eslintrc": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.16.tgz", - "integrity": "sha512-CYkxVvkZzGCqFrt7EgjFxQKhlUPyDkuR9P0Y5wEcmJqVI8ncerOIY5Kej52MhZyzOBXkYrJgZeVZC9xXXoEg9A==", - "cpu": [ - "x64" - ], + "node_modules/@eslint/js": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.16.tgz", - "integrity": "sha512-fxrw4BYqQ39z/3Ja9xj/a1gMsVq0xEjhSyI4a9MjfvDDD8fUV8IYliac96i7tzZc3+VytyXX+XNsnpEk5sw5Wg==", - "cpu": [ - "arm64" - ], + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, "engines": { - "node": ">=12" + "node": ">=10.10.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.16.tgz", - "integrity": "sha512-8p3v1D+du2jiDvSoNVimHhj7leSfST9YlKsAEO7etBfuqjaBMndo0fmjNLp0JCMld+XIx9L80tooOkyUv1a1PQ==", - "cpu": [ - "x64" - ], + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "optional": true, - "os": [ - "freebsd" - ], "engines": { - "node": ">=12" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.16.tgz", - "integrity": "sha512-bYaocE1/PTMRmkgSckZ0D0Xn2nox8v2qlk+MVVqm+VECNKDdZvghVZtH41dNtBbwADSvA6qkCHGYeWm9LrNCBw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.16.tgz", - "integrity": "sha512-N3u6BBbCVY3xeP2D8Db7QY8I+nZ+2AgOopUIqk+5yCoLnsWkcVxD2ay5E9iIdvApFi1Vg1lZiiwaVp8bOpAc4A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.16.tgz", - "integrity": "sha512-dxjqLKUW8GqGemoRT9v8IgHk+T4tRm1rn1gUcArsp26W9EkK/27VSjBVUXhEG5NInHZ92JaQ3SSMdTwv/r9a2A==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.16.tgz", - "integrity": "sha512-MdUFggHjRiCCwNE9+1AibewoNq6wf94GLB9Q9aXwl+a75UlRmbRK3h6WJyrSGA6ZstDJgaD2wiTSP7tQNUYxwA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.16.tgz", - "integrity": "sha512-CO3YmO7jYMlGqGoeFeKzdwx/bx8Vtq/SZaMAi+ZLDUnDUdfC7GmGwXzIwDJ70Sg+P9pAemjJyJ1icKJ9R3q/Fg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.16.tgz", - "integrity": "sha512-DSl5Czh5hCy/7azX0Wl9IdzPHX2H8clC6G87tBnZnzUpNgRxPFhfmArbaHoAysu4JfqCqbB/33u/GL9dUgCBAw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.16.tgz", - "integrity": "sha512-sSVVMEXsqf1fQu0j7kkhXMViroixU5XoaJXl1u/u+jbXvvhhCt9YvA/B6VM3aM/77HuRQ94neS5bcisijGnKFQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.16.tgz", - "integrity": "sha512-jRqBCre9gZGoCdCN/UWCCMwCMsOg65IpY9Pyj56mKCF5zXy9d60kkNRdDN6YXGjr3rzcC4DXnS/kQVCGcC4yPQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.16.tgz", - "integrity": "sha512-G1+09TopOzo59/55lk5Q0UokghYLyHTKKzD5lXsAOOlGDbieGEFJpJBr3BLDbf7cz89KX04sBeExAR/pL/26sA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.16.tgz", - "integrity": "sha512-xwjGJB5wwDEujLaJIrSMRqWkbigALpBNcsF9SqszoNKc+wY4kPTdKrSxiY5ik3IatojePP+WV108MvF6q6np4w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.16.tgz", - "integrity": "sha512-yeERkoxG2nR2oxO5n+Ms7MsCeNk23zrby2GXCqnfCpPp7KNc0vxaaacIxb21wPMfXXRhGBrNP4YLIupUBrWdlg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.16.tgz", - "integrity": "sha512-nHfbEym0IObXPhtX6Va3H5GaKBty2kdhlAhKmyCj9u255ktAj0b1YACUs9j5H88NRn9cJCthD1Ik/k9wn8YKVg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.16.tgz", - "integrity": "sha512-pdD+M1ZOFy4hE15ZyPX09fd5g4DqbbL1wXGY90YmleVS6Y5YlraW4BvHjim/X/4yuCpTsAFvsT4Nca2lbyDH/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.16.tgz", - "integrity": "sha512-IPEMfU9p0c3Vb8PqxaPX6BM9rYwlTZGYOf9u+kMdhoILZkVKEjq6PKZO0lB+isojWwAnAqh4ZxshD96njTXajg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.16.tgz", - "integrity": "sha512-1YYpoJ39WV/2bnShPwgdzJklc+XS0bysN6Tpnt1cWPdeoKOG4RMEY1g7i534QxXX/rPvNx/NLJQTTCeORYzipg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.4.tgz", - "integrity": "sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.0.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz", - "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "node_modules/@influxdata/influxdb-client": { + "version": "1.33.2", + "resolved": "https://registry.npmjs.org/@influxdata/influxdb-client/-/influxdb-client-1.33.2.tgz", + "integrity": "sha512-RT5SxH+grHAazo/YK3UTuWK/frPWRM0N7vkrCUyqVprDgQzlLP+bSK4ak2Jv3QVF/pazTnsxWjvtKZdwskV5Xw==", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -991,6 +847,19 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -1004,6 +873,45 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -1040,75 +948,75 @@ } }, "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@jest/console/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@jest/console/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/console/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/@jest/console/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/@jest/core": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", @@ -1157,74 +1065,53 @@ } } }, - "node_modules/@jest/core/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@jest/core/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@jest/core/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/@jest/core/node_modules/pretty-format": { @@ -1242,36 +1129,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/core/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@jest/core/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/environment": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", @@ -1301,26 +1164,17 @@ } }, "node_modules/@jest/expect-utils": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.0.1.tgz", - "integrity": "sha512-Tw5kUUOKmXGQDmQ9TSgTraFFS7HMC1HG/B7y0AN2G2UzjdAXz9BzK2rmNpCSDl7g7y0Gf/VLBm//blonvhtOTQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "dependencies": { - "jest-get-type": "^29.0.0" + "jest-get-type": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/expect-utils/node_modules/jest-get-type": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz", - "integrity": "sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jest/expect/node_modules/@jest/expect-utils": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", @@ -1333,6 +1187,27 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/@jest/expect/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/expect/node_modules/diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, "node_modules/@jest/expect/node_modules/expect": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", @@ -1349,6 +1224,21 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/@jest/expect/node_modules/jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, "node_modules/@jest/expect/node_modules/jest-get-type": { "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", @@ -1358,56 +1248,199 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/fake-timers": { + "node_modules/@jest/expect/node_modules/jest-matcher-utils": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/globals": { + "node_modules/@jest/expect/node_modules/jest-message-util": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/reporters": { + "node_modules/@jest/expect/node_modules/jest-util": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", "@types/node": "*", "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/fake-timers/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/@jest/globals": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", + "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/types": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", + "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^5.1.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", @@ -1434,54 +1467,17 @@ } }, "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/@jest/reporters/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -1502,37 +1498,74 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@jest/reporters/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/reporters/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/@jest/reporters/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/@jest/source-map": { @@ -1605,74 +1638,21 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@jest/transform/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/@jest/types": { @@ -1692,105 +1672,24 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/types/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true, "engines": { "node": ">=6.0.0" @@ -1806,19 +1705,31 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "dev": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@nodelib/fs.scandir": { @@ -1857,31 +1768,30 @@ } }, "node_modules/@playwright/test": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.25.2.tgz", - "integrity": "sha512-6qPznIR4Fw02OMbqXUPMG6bFFg1hDVNEdihKy0t9K0dmRbus1DyP5Q5XFQhGwEHQkLG5hrSfBuu9CW/foqhQHQ==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", + "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", "dev": true, "dependencies": { - "@types/node": "*", - "playwright-core": "1.25.2" + "playwright": "1.39.0" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/@sinclair/typebox": { - "version": "0.24.28", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.28.tgz", - "integrity": "sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==", + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", "dev": true }, "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", "dev": true, "dependencies": { "type-detect": "4.0.8" @@ -1897,31 +1807,31 @@ } }, "node_modules/@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "version": "7.20.4", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.4.tgz", + "integrity": "sha512-mLnSC22IC4vcWiuObSRjrLd9XcBTGf59vUSoq2jkQDJ/QQ8PMI9rSuzE+aEV8karUMbskw07bKYoUJCKTUaygg==", "dev": true, "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "version": "7.6.7", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.7.tgz", + "integrity": "sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==", "dev": true, "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "dependencies": { "@babel/parser": "^7.1.0", @@ -1929,18 +1839,18 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.0.tgz", - "integrity": "sha512-v4Vwdko+pgymgS+A2UIaJru93zQd85vIGWObM5ekZNdXCKtDYqATlEYnWgfo86Q6I1Lh0oXnksDnMU1cwmlPDw==", + "version": "7.20.4", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz", + "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==", "dev": true, "dependencies": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.20.7" } }, "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -1953,91 +1863,102 @@ "dev": true }, "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, "dependencies": { "@types/istanbul-lib-report": "*" } }, "node_modules/@types/jest": { - "version": "27.0.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.3.tgz", - "integrity": "sha512-cmmwv9t7gBYt7hNKH5Spu7Kuu/DotGa+Ff+JGRKZ4db5eh8PnKS4LuebJ3YLUoyOyIHraTGyULn23YtEAm0VSg==", + "version": "27.5.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", + "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", "dev": true, "dependencies": { - "jest-diff": "^27.0.0", + "jest-matcher-utils": "^27.0.0", "pretty-format": "^27.0.0" } }, "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/node": { - "version": "18.14.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz", - "integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==", - "dev": true + "version": "18.18.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz", + "integrity": "sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/prettier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", - "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", + "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", "dev": true }, "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, "node_modules/@types/yargs": { - "version": "17.0.11", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz", - "integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==", + "version": "17.0.31", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.31.tgz", + "integrity": "sha512-bocYSx4DI8TmdlvxqGpVNXOgCNR1Jj0gNPhhAY+iz1rgKDAaYrAYdFYnhDV1IFuiuVc9HkOwyDcFxaTElF3/wg==", "dev": true, "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.5.0.tgz", - "integrity": "sha512-4bV6fulqbuaO9UMXU0Ia0o6z6if+kmMRW8rMRyfqXj/eGrZZRGedS4n0adeGNnjr8LKAM495hrQ7Tea52UWmQA==", - "dev": true, - "dependencies": { - "@typescript-eslint/experimental-utils": "5.5.0", - "@typescript-eslint/scope-manager": "5.5.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "engines": { @@ -2057,54 +1978,42 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "peer": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "engines": { - "node": ">= 6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.5.0.tgz", - "integrity": "sha512-kjWeeVU+4lQ1SLYErRKV5yDXbWDPkpbzTUUlfAUifPYvpX0qZlrcCZ96/6oWxt3QxtK5WVhXz+KsnwW9cIW+3A==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.5.0", - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/typescript-estree": "5.5.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2112,22 +2021,18 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" } }, - "node_modules/@typescript-eslint/parser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.5.0.tgz", - "integrity": "sha512-JsXBU+kgQOAgzUn2jPrLA+Rd0Y1dswOlX3hp8MuRO1hQDs6xgHtbCXEiAu7bz5hyVURxbXcA2draasMbNqrhmg==", + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, - "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.5.0", - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/typescript-estree": "5.5.0", - "debug": "^4.3.2" + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2137,7 +2042,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "*" }, "peerDependenciesMeta": { "typescript": { @@ -2145,27 +2050,10 @@ } } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.5.0.tgz", - "integrity": "sha512-0/r656RmRLo7CbN4Mdd+xZyPJ/fPCKhYdU6mnZx+8msAD8nJSP8EyCFkzbd6vNVZzZvWlMYrSNekqGrCBqFQhg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/visitor-keys": "5.5.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/types": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.5.0.tgz", - "integrity": "sha512-OaYTqkW3GnuHxqsxxJ6KypIKd5Uw7bFiQJZRyNi1jbMJnK3Hc/DR4KwB6KJj6PBRkJJoaNwzMNv9vtTk87JhOg==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2176,17 +2064,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.5.0.tgz", - "integrity": "sha512-pVn8btYUiYrjonhMAO0yG8lm7RApzy2L4RC7Td/mC/qFkyf6vRbGyZozoA94+w6D2Y2GRqpMoCWcwx/EUOzyoQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/visitor-keys": "5.5.0", - "debug": "^4.3.2", - "globby": "^11.0.4", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "engines": { @@ -2202,50 +2090,40 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" }, "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "engines": { - "node": ">= 6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.5.0.tgz", - "integrity": "sha512-4GzJ1kRtsWzHhdM40tv0ZKHNSbkDhF0Woi/TDwVJX6UICwJItvP7ZTXbjTkCdrors7ww0sYe0t+cIKDAJwZ7Kw==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.5.0", - "eslint-visitor-keys": "^3.0.0" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2255,10 +2133,16 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/acorn": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", - "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -2292,15 +2176,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -2316,6 +2191,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2326,27 +2213,30 @@ } }, "node_modules/ansi-sequence-parser": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz", - "integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", "dev": true }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "dependencies": { "normalize-path": "^3.0.0", @@ -2392,76 +2282,6 @@ "@babel/core": "^7.8.0" } }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -2566,9 +2386,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", - "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, "funding": [ { @@ -2578,13 +2398,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001370", - "electron-to-chromium": "^1.4.202", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.5" + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -2620,6 +2444,14 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/cachedir": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2639,9 +2471,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001384", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001384.tgz", - "integrity": "sha512-BBWt57kqWbc0GYZXb47wTXpmAgqr5LSibPzNjk/AWMdmJMQhLqOl3c/Kd4OAU/tu4NLfYkMx8Tlq3RVBkOBolQ==", + "version": "1.0.30001562", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001562.tgz", + "integrity": "sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng==", "dev": true, "funding": [ { @@ -2651,21 +2483,27 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/char-regex": { @@ -2678,26 +2516,38 @@ } }, "node_modules/ci-info": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", - "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", - "dev": true + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } }, "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/co": { @@ -2711,40 +2561,40 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -2761,9 +2611,9 @@ } }, "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2790,22 +2640,14 @@ "dev": true }, "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/detect-gpu": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.5.tgz", - "integrity": "sha512-gKBKx8mj0Fi/8DnjuzxU+aNYF1yWvPxtiOV9o72539wW0HP3ZTJVol/0FUasvdxIY1Bhi19SwIINqXGO+RB/sA==", - "dependencies": { - "webgl-constants": "^1.1.1" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2849,9 +2691,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.233", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.233.tgz", - "integrity": "sha512-ejwIKXTg1wqbmkcRJh9Ur3hFGHFDZDw1POzdsVrB2WZjgRuRMHIQQKNpe64N/qh3ZtH2otEoRoS+s6arAAuAAw==", + "version": "1.4.583", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.583.tgz", + "integrity": "sha512-93y1gcONABZ7uqYe/JWDVQP/Pj/sQSunF0HVAPdlg/pfBnOyBMLlQUxWvkqcljJg1+W6cjvPuYD+r1Th9Tn8mA==", "dev": true }, "node_modules/emittery": { @@ -2872,18 +2714,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2894,9 +2724,9 @@ } }, "node_modules/esbuild": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.16.tgz", - "integrity": "sha512-24JyKq10KXM5EBIgPotYIJ2fInNWVVqflv3gicIyQqfmUqi4HvDW1VR790cBgLJHCl96Syy7lhoz7tLFcmuRmg==", + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.5.tgz", + "integrity": "sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==", "dev": true, "hasInstallScript": true, "bin": { @@ -2906,28 +2736,28 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.16.16", - "@esbuild/android-arm64": "0.16.16", - "@esbuild/android-x64": "0.16.16", - "@esbuild/darwin-arm64": "0.16.16", - "@esbuild/darwin-x64": "0.16.16", - "@esbuild/freebsd-arm64": "0.16.16", - "@esbuild/freebsd-x64": "0.16.16", - "@esbuild/linux-arm": "0.16.16", - "@esbuild/linux-arm64": "0.16.16", - "@esbuild/linux-ia32": "0.16.16", - "@esbuild/linux-loong64": "0.16.16", - "@esbuild/linux-mips64el": "0.16.16", - "@esbuild/linux-ppc64": "0.16.16", - "@esbuild/linux-riscv64": "0.16.16", - "@esbuild/linux-s390x": "0.16.16", - "@esbuild/linux-x64": "0.16.16", - "@esbuild/netbsd-x64": "0.16.16", - "@esbuild/openbsd-x64": "0.16.16", - "@esbuild/sunos-x64": "0.16.16", - "@esbuild/win32-arm64": "0.16.16", - "@esbuild/win32-ia32": "0.16.16", - "@esbuild/win32-x64": "0.16.16" + "@esbuild/android-arm": "0.19.5", + "@esbuild/android-arm64": "0.19.5", + "@esbuild/android-x64": "0.19.5", + "@esbuild/darwin-arm64": "0.19.5", + "@esbuild/darwin-x64": "0.19.5", + "@esbuild/freebsd-arm64": "0.19.5", + "@esbuild/freebsd-x64": "0.19.5", + "@esbuild/linux-arm": "0.19.5", + "@esbuild/linux-arm64": "0.19.5", + "@esbuild/linux-ia32": "0.19.5", + "@esbuild/linux-loong64": "0.19.5", + "@esbuild/linux-mips64el": "0.19.5", + "@esbuild/linux-ppc64": "0.19.5", + "@esbuild/linux-riscv64": "0.19.5", + "@esbuild/linux-s390x": "0.19.5", + "@esbuild/linux-x64": "0.19.5", + "@esbuild/netbsd-x64": "0.19.5", + "@esbuild/openbsd-x64": "0.19.5", + "@esbuild/sunos-x64": "0.19.5", + "@esbuild/win32-arm64": "0.19.5", + "@esbuild/win32-ia32": "0.19.5", + "@esbuild/win32-x64": "0.19.5" } }, "node_modules/escalade": { @@ -2940,58 +2770,61 @@ } }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.3.0.tgz", - "integrity": "sha512-aIay56Ph6RxOTC7xyr59Kt3ewX185SaGnAr8eWukoPLeriCrvGjvAubxuvaXOfsxhtwV5g0uBOsyhAom4qJdww==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.0.4", - "@humanwhocodes/config-array": "^0.6.0", - "ajv": "^6.10.0", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.0", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.1.0", - "espree": "^9.1.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.2.0", - "semver": "^7.2.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" @@ -3016,234 +2849,83 @@ "node": ">=8.0.0" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", - "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=4.0" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=4" } }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", - "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.1.0.tgz", - "integrity": "sha512-ZgYLvCS1wxOczBYGcQT9DDWgicXwJ4dbocr9uYN+/eresBAUuBu+O4WzB21ufQ/JqQT8gyp7hJ3z8SHii32mTQ==", - "dev": true, - "dependencies": { - "acorn": "^8.6.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.1.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" + "node": ">=0.10" } }, "node_modules/esquery/node_modules/estraverse": { @@ -3327,188 +3009,97 @@ } }, "node_modules/expect": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.0.1.tgz", - "integrity": "sha512-yQgemsjLU+1S8t2A7pXT3Sn/v5/37LY8J+tocWtKEA0iEYYc6gfKbbJJX2fxHZmd7K9WpdbQqXUpmYkq1aewYg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.0.1", - "jest-get-type": "^29.0.0", - "jest-matcher-utils": "^29.0.1", - "jest-message-util": "^29.0.1", - "jest-util": "^29.0.1" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/expect/node_modules/@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", + "node_modules/expect/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/expect/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "node_modules/expect/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true }, - "node_modules/expect/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/expect/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/expect/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/expect/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/expect/node_modules/diff-sequences": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.0.0.tgz", - "integrity": "sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/expect/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/expect/node_modules/jest-diff": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.0.1.tgz", - "integrity": "sha512-l8PYeq2VhcdxG9tl5cU78ClAlg/N7RtVSp0v3MlXURR0Y99i6eFnegmasOandyTmO6uEdo20+FByAjBFEO9nuw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.0.0", - "jest-get-type": "^29.0.0", - "pretty-format": "^29.0.1" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/expect/node_modules/jest-get-type": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz", - "integrity": "sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/expect/node_modules/jest-matcher-utils": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.0.1.tgz", - "integrity": "sha512-/e6UbCDmprRQFnl7+uBKqn4G22c/OmwriE5KCMVqxhElKCQUDcFnq5XM9iJeKtzy4DUjxT27y9VHmKPD8BQPaw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.0.1", - "jest-get-type": "^29.0.0", - "pretty-format": "^29.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/expect/node_modules/jest-message-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.1.tgz", - "integrity": "sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.0.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.0.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/expect/node_modules/jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "dependencies": { - "@jest/types": "^29.0.1", - "@types/node": "*", "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/expect/node_modules/pretty-format": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.1.tgz", - "integrity": "sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "@jest/schemas": "^29.0.0", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -3516,36 +3107,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/expect/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/expect/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/expect/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3553,9 +3120,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -3565,7 +3132,7 @@ "micromatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=8.6.0" } }, "node_modules/fast-glob/node_modules/glob-parent": { @@ -3589,22 +3156,22 @@ "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "dependencies": { "reusify": "^1.0.4" } }, "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, "dependencies": { "bser": "2.1.1" @@ -3635,25 +3202,29 @@ } }, "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { @@ -3661,15 +3232,15 @@ } }, "node_modules/flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, "node_modules/fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", @@ -3683,35 +3254,18 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3752,9 +3306,9 @@ } }, "node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -3792,9 +3346,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz", - "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -3804,25 +3358,31 @@ } }, "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" }, "engines": { @@ -3833,19 +3393,25 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "dependencies": { "minimist": "^1.2.5", - "neo-async": "^2.6.0", + "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, @@ -3859,25 +3425,25 @@ "uglify-js": "^3.1.4" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, "engines": { - "node": ">= 0.4.0" + "node": ">=8" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" } }, "node_modules/howslow": { @@ -3902,9 +3468,9 @@ } }, "node_modules/ignore": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", - "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { "node": ">= 4" @@ -3948,7 +3514,7 @@ "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "engines": { "node": ">=0.8.19" @@ -3957,7 +3523,7 @@ "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "dependencies": { "once": "^1.3.0", @@ -3977,12 +3543,12 @@ "dev": true }, "node_modules/is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3991,7 +3557,7 @@ "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "engines": { "node": ">=0.10.0" @@ -4036,6 +3602,15 @@ "node": ">=0.12.0" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -4051,7 +3626,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, "node_modules/isomorphic-fetch": { @@ -4063,57 +3638,19 @@ "whatwg-fetch": "^3.4.1" } }, - "node_modules/isomorphic-fetch/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/isomorphic-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "node_modules/isomorphic-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - }, - "node_modules/isomorphic-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "dependencies": { "@babel/core": "^7.12.3", @@ -4126,39 +3663,27 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "engines": { - "node": ">=8" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/istanbul-lib-source-maps": { @@ -4176,9 +3701,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -4257,74 +3782,101 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-circus/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" + "engines": { + "node": ">=10" }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-circus/node_modules/jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-circus/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-circus/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=7.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-circus/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/jest-circus/node_modules/pretty-format": { @@ -4342,36 +3894,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-circus/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-circus/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-cli": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", @@ -4406,80 +3934,27 @@ } } }, - "node_modules/jest-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-cli/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", - "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", + "node_modules/jest-config": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", + "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", @@ -4521,67 +3996,18 @@ } } }, - "node_modules/jest-config/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/jest-config/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -4602,15 +4028,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-config/node_modules/jest-get-type": { "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", @@ -4620,6 +4037,23 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/jest-config/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, "node_modules/jest-config/node_modules/pretty-format": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", @@ -4635,36 +4069,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-config/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-config/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-diff": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", @@ -4680,74 +4090,13 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-diff/node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-docblock": { @@ -4778,83 +4127,42 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-each/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-each/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-each/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/jest-each/node_modules/pretty-format": { @@ -4872,36 +4180,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-each/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-each/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-environment-node": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", @@ -4919,13 +4203,30 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/jest-environment-node/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, "node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { @@ -4953,26 +4254,31 @@ "fsevents": "^2.3.2" } }, - "node_modules/jest-leak-detector": { + "node_modules/jest-haste-map/node_modules/jest-util": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", - "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-leak-detector/node_modules/@jest/schemas": { + "node_modules/jest-leak-detector": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", + "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.24.1" + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" @@ -5021,259 +4327,287 @@ "dev": true }, "node_modules/jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "node_modules/jest-matcher-utils/node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-message-util/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-message-util/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/jest-message-util/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, - "node_modules/jest-matcher-utils/node_modules/diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/jest-diff": { + "node_modules/jest-message-util/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-mock": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" + "@jest/types": "^28.1.3", + "@types/node": "*" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/jest-get-type": { + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", "dev": true, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "node_modules/jest-resolve": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", + "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-resolve-dependencies": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", + "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "jest-regex-util": "^28.0.2", + "jest-snapshot": "^28.1.3" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-message-util": { + "node_modules/jest-resolve/node_modules/jest-util": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.12.13", "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", + "@types/node": "*", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "picomatch": "^2.2.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-message-util/node_modules/@jest/schemas": { + "node_modules/jest-runner": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", + "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.24.1" + "@jest/console": "^28.1.3", + "@jest/environment": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^28.1.1", + "jest-environment-node": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-leak-detector": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-resolve": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-util": "^28.1.3", + "jest-watcher": "^28.1.3", + "jest-worker": "^28.1.3", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-runner/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-runner/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-message-util/node_modules/pretty-format": { + "node_modules/jest-runner/node_modules/pretty-format": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", @@ -5288,6952 +4622,283 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/react-is": { + "node_modules/jest-runner/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock": { + "node_modules/jest-runtime": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", + "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", "dev": true, "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/globals": "^28.1.3", + "@jest/source-map": "^28.1.2", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", "@jest/types": "^28.1.3", - "@types/node": "*" + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" + "node": ">=10" }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-resolve": { + "node_modules/jest-runtime/node_modules/jest-message-util": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", - "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-resolve-dependencies": { + "node_modules/jest-runtime/node_modules/jest-util": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", - "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "jest-regex-util": "^28.0.2", - "jest-snapshot": "^28.1.3" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-runtime/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-runtime/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-snapshot": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", + "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", - "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", - "dev": true, - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/environment": "^28.1.3", - "@jest/test-result": "^28.1.3", + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^28.1.3", "@jest/transform": "^28.1.3", "@jest/types": "^28.1.3", - "@types/node": "*", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "emittery": "^0.10.2", + "expect": "^28.1.3", "graceful-fs": "^4.2.9", - "jest-docblock": "^28.1.1", - "jest-environment-node": "^28.1.3", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", "jest-haste-map": "^28.1.3", - "jest-leak-detector": "^28.1.3", + "jest-matcher-utils": "^28.1.3", "jest-message-util": "^28.1.3", - "jest-resolve": "^28.1.3", - "jest-runtime": "^28.1.3", "jest-util": "^28.1.3", - "jest-watcher": "^28.1.3", - "jest-worker": "^28.1.3", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" + "natural-compare": "^1.4.0", + "pretty-format": "^28.1.3", + "semver": "^7.3.5" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-snapshot/node_modules/@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "jest-get-type": "^28.0.2" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-snapshot/node_modules/diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-runtime": { + "node_modules/jest-snapshot/node_modules/expect": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", - "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", "dev": true, "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/globals": "^28.1.3", - "@jest/source-map": "^28.1.2", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" + "jest-util": "^28.1.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-snapshot/node_modules/jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-snapshot/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-snapshot/node_modules/jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, "engines": { - "node": ">=7.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runtime/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/jest-snapshot/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", - "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "leven": "^3.1.0", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-validate/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-validate/node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", - "dev": true, - "dependencies": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-sha256": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", - "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "dev": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/playwright-core": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.25.2.tgz", - "integrity": "sha512-0yTbUE9lIddkEpLHL3u8PoCL+pWiZtj5A/j3U7YoNjcmKKDGBnCrgHJMzwd2J5vy6l28q4ki3JIuz7McLHhl1A==", - "dev": true, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", - "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/replace-in-file": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.3.5.tgz", - "integrity": "sha512-arB9d3ENdKva2fxRnSjwBEXfK1npgyci7ZZuwysgAp7ORjHSyxz6oqIjTEv8R0Ydl4Ll7uOAZXL4vbkhGIizCg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.2", - "glob": "^7.2.0", - "yargs": "^17.2.1" - }, - "bin": { - "replace-in-file": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/replace-in-file/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/replace-in-file/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/replace-in-file/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/replace-in-file/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/replace-in-file/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/replace-in-file/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/replace-in-file/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shiki": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.1.tgz", - "integrity": "sha512-+Jz4nBkCBe0mEDqo1eKRcCdjRtrCjozmcbTUjbPTX7OOJfEbTZzlUWlZtGe3Gb5oV1/jnojhG//YZc3rs9zSEw==", - "dev": true, - "dependencies": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-jest": { - "version": "28.0.8", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", - "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^28.0.0", - "json5": "^2.2.1", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^28.0.0", - "babel-jest": "^28.0.0", - "jest": "^28.0.0", - "typescript": ">=4.3" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedoc": { - "version": "0.24.6", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.6.tgz", - "integrity": "sha512-c3y3h45xJv3qYwKDAwU6Cl+26CjT0ZvblHzfHJ+SjQDM4p1mZxtgHky4lhmG0+nNarRht8kADfZlbspJWdZarQ==", - "dev": true, - "dependencies": { - "lunr": "^2.3.9", - "marked": "^4.3.0", - "minimatch": "^9.0.0", - "shiki": "^0.14.1" - }, - "bin": { - "typedoc": "bin/typedoc" - }, - "engines": { - "node": ">= 14.14" - }, - "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x" - } - }, - "node_modules/typedoc-plugin-markdown": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.15.3.tgz", - "integrity": "sha512-idntFYu3vfaY3eaD+w9DeRd0PmNGqGuNLKihPU9poxFGnATJYGn9dPtEhn2QrTdishFMg7jPXAhos+2T6YCWRQ==", - "dev": true, - "dependencies": { - "handlebars": "^4.7.7" - }, - "peerDependencies": { - "typedoc": ">=0.24.0" - } - }, - "node_modules/typedoc-plugin-merge-modules": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/typedoc-plugin-merge-modules/-/typedoc-plugin-merge-modules-5.0.1.tgz", - "integrity": "sha512-7fiMYDUaeslsGSFDevw+azhD0dFJce0h2g5UuQ8zXljoky+YfmzoNkoTCx+KWaNJo6rz2DzaD2feVJyUhvUegg==", - "dev": true, - "peerDependencies": { - "typedoc": "0.24.x" - } - }, - "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", - "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", - "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true - }, - "node_modules/vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/webgl-constants": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", - "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" - }, - "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/compat-data": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.13.tgz", - "integrity": "sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==", - "dev": true - }, - "@babel/core": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz", - "integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helpers": "^7.18.9", - "@babel/parser": "^7.18.13", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.13", - "@babel/types": "^7.18.13", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - } - }, - "@babel/generator": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.13.tgz", - "integrity": "sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==", - "dev": true, - "requires": { - "@babel/types": "^7.18.13", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz", - "integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.20.2", - "semver": "^6.3.0" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", - "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", - "dev": true, - "requires": { - "@babel/template": "^7.18.6", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", - "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", - "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", - "dev": true - }, - "@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true - }, - "@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", - "dev": true, - "requires": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz", - "integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==", - "dev": true - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" - } - }, - "@babel/traverse": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.13.tgz", - "integrity": "sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.18.13", - "@babel/types": "^7.18.13", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz", - "integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@esbuild/android-arm": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.16.tgz", - "integrity": "sha512-BUuWMlt4WSXod1HSl7aGK8fJOsi+Tab/M0IDK1V1/GstzoOpqc/v3DqmN8MkuapPKQ9Br1WtLAN4uEgWR8x64A==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.16.tgz", - "integrity": "sha512-hFHVAzUKp9Tf8psGq+bDVv+6hTy1bAOoV/jJMUWwhUnIHsh6WbFMhw0ZTkqDuh7TdpffFoHOiIOIxmHc7oYRBQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.16.tgz", - "integrity": "sha512-9WhxJpeb6XumlfivldxqmkJepEcELekmSw3NkGrs+Edq6sS5KRxtUBQuKYDD7KqP59dDkxVbaoPIQFKWQG0KLg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.16.tgz", - "integrity": "sha512-8Z+wld+vr/prHPi2O0X7o1zQOfMbXWGAw9hT0jEyU/l/Yrg+0Z3FO9pjPho72dVkZs4ewZk0bDOFLdZHm8jEfw==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.16.tgz", - "integrity": "sha512-CYkxVvkZzGCqFrt7EgjFxQKhlUPyDkuR9P0Y5wEcmJqVI8ncerOIY5Kej52MhZyzOBXkYrJgZeVZC9xXXoEg9A==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.16.tgz", - "integrity": "sha512-fxrw4BYqQ39z/3Ja9xj/a1gMsVq0xEjhSyI4a9MjfvDDD8fUV8IYliac96i7tzZc3+VytyXX+XNsnpEk5sw5Wg==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.16.tgz", - "integrity": "sha512-8p3v1D+du2jiDvSoNVimHhj7leSfST9YlKsAEO7etBfuqjaBMndo0fmjNLp0JCMld+XIx9L80tooOkyUv1a1PQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.16.tgz", - "integrity": "sha512-bYaocE1/PTMRmkgSckZ0D0Xn2nox8v2qlk+MVVqm+VECNKDdZvghVZtH41dNtBbwADSvA6qkCHGYeWm9LrNCBw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.16.tgz", - "integrity": "sha512-N3u6BBbCVY3xeP2D8Db7QY8I+nZ+2AgOopUIqk+5yCoLnsWkcVxD2ay5E9iIdvApFi1Vg1lZiiwaVp8bOpAc4A==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.16.tgz", - "integrity": "sha512-dxjqLKUW8GqGemoRT9v8IgHk+T4tRm1rn1gUcArsp26W9EkK/27VSjBVUXhEG5NInHZ92JaQ3SSMdTwv/r9a2A==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.16.tgz", - "integrity": "sha512-MdUFggHjRiCCwNE9+1AibewoNq6wf94GLB9Q9aXwl+a75UlRmbRK3h6WJyrSGA6ZstDJgaD2wiTSP7tQNUYxwA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.16.tgz", - "integrity": "sha512-CO3YmO7jYMlGqGoeFeKzdwx/bx8Vtq/SZaMAi+ZLDUnDUdfC7GmGwXzIwDJ70Sg+P9pAemjJyJ1icKJ9R3q/Fg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.16.tgz", - "integrity": "sha512-DSl5Czh5hCy/7azX0Wl9IdzPHX2H8clC6G87tBnZnzUpNgRxPFhfmArbaHoAysu4JfqCqbB/33u/GL9dUgCBAw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.16.tgz", - "integrity": "sha512-sSVVMEXsqf1fQu0j7kkhXMViroixU5XoaJXl1u/u+jbXvvhhCt9YvA/B6VM3aM/77HuRQ94neS5bcisijGnKFQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.16.tgz", - "integrity": "sha512-jRqBCre9gZGoCdCN/UWCCMwCMsOg65IpY9Pyj56mKCF5zXy9d60kkNRdDN6YXGjr3rzcC4DXnS/kQVCGcC4yPQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.16.tgz", - "integrity": "sha512-G1+09TopOzo59/55lk5Q0UokghYLyHTKKzD5lXsAOOlGDbieGEFJpJBr3BLDbf7cz89KX04sBeExAR/pL/26sA==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.16.tgz", - "integrity": "sha512-xwjGJB5wwDEujLaJIrSMRqWkbigALpBNcsF9SqszoNKc+wY4kPTdKrSxiY5ik3IatojePP+WV108MvF6q6np4w==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.16.tgz", - "integrity": "sha512-yeERkoxG2nR2oxO5n+Ms7MsCeNk23zrby2GXCqnfCpPp7KNc0vxaaacIxb21wPMfXXRhGBrNP4YLIupUBrWdlg==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.16.tgz", - "integrity": "sha512-nHfbEym0IObXPhtX6Va3H5GaKBty2kdhlAhKmyCj9u255ktAj0b1YACUs9j5H88NRn9cJCthD1Ik/k9wn8YKVg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.16.tgz", - "integrity": "sha512-pdD+M1ZOFy4hE15ZyPX09fd5g4DqbbL1wXGY90YmleVS6Y5YlraW4BvHjim/X/4yuCpTsAFvsT4Nca2lbyDH/A==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.16.tgz", - "integrity": "sha512-IPEMfU9p0c3Vb8PqxaPX6BM9rYwlTZGYOf9u+kMdhoILZkVKEjq6PKZO0lB+isojWwAnAqh4ZxshD96njTXajg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.16.tgz", - "integrity": "sha512-1YYpoJ39WV/2bnShPwgdzJklc+XS0bysN6Tpnt1cWPdeoKOG4RMEY1g7i534QxXX/rPvNx/NLJQTTCeORYzipg==", - "dev": true, - "optional": true - }, - "@eslint/eslintrc": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.4.tgz", - "integrity": "sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.0.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@humanwhocodes/config-array": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz", - "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", - "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/reporters": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^28.1.3", - "jest-config": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-resolve-dependencies": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "jest-watcher": "^28.1.3", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", - "dev": true, - "requires": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" - }, - "dependencies": { - "@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2" - } - }, - "expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "requires": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - } - } - }, - "@jest/expect-utils": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.0.1.tgz", - "integrity": "sha512-Tw5kUUOKmXGQDmQ9TSgTraFFS7HMC1HG/B7y0AN2G2UzjdAXz9BzK2rmNpCSDl7g7y0Gf/VLBm//blonvhtOTQ==", - "dev": true, - "requires": { - "jest-get-type": "^29.0.0" - }, - "dependencies": { - "jest-get-type": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz", - "integrity": "sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==", - "dev": true - } - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "@jest/globals": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" - } - }, - "@jest/reporters": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/source-map": { - "version": "28.1.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", - "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.13", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", - "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", - "dev": true, - "requires": { - "@jest/test-result": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", - "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@playwright/test": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.25.2.tgz", - "integrity": "sha512-6qPznIR4Fw02OMbqXUPMG6bFFg1hDVNEdihKy0t9K0dmRbus1DyP5Q5XFQhGwEHQkLG5hrSfBuu9CW/foqhQHQ==", - "dev": true, - "requires": { - "@types/node": "*", - "playwright-core": "1.25.2" - } - }, - "@sinclair/typebox": { - "version": "0.24.28", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.28.tgz", - "integrity": "sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.0.tgz", - "integrity": "sha512-v4Vwdko+pgymgS+A2UIaJru93zQd85vIGWObM5ekZNdXCKtDYqATlEYnWgfo86Q6I1Lh0oXnksDnMU1cwmlPDw==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/isomorphic-fetch": { - "version": "0.0.36", - "resolved": "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.36.tgz", - "integrity": "sha512-ulw4d+vW1HKn4oErSmNN2HYEcHGq0N1C5exlrMM0CRqX1UUpFhGb5lwiom5j9KN3LBJJDLRmYIZz1ghm7FIzZw==", - "dev": true - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "27.0.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.3.tgz", - "integrity": "sha512-cmmwv9t7gBYt7hNKH5Spu7Kuu/DotGa+Ff+JGRKZ4db5eh8PnKS4LuebJ3YLUoyOyIHraTGyULn23YtEAm0VSg==", - "dev": true, - "requires": { - "jest-diff": "^27.0.0", - "pretty-format": "^27.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "@types/node": { - "version": "18.14.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz", - "integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==", - "dev": true - }, - "@types/prettier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", - "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/yargs": { - "version": "17.0.11", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz", - "integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.5.0.tgz", - "integrity": "sha512-4bV6fulqbuaO9UMXU0Ia0o6z6if+kmMRW8rMRyfqXj/eGrZZRGedS4n0adeGNnjr8LKAM495hrQ7Tea52UWmQA==", - "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "5.5.0", - "@typescript-eslint/scope-manager": "5.5.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } - } - }, - "@typescript-eslint/experimental-utils": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.5.0.tgz", - "integrity": "sha512-kjWeeVU+4lQ1SLYErRKV5yDXbWDPkpbzTUUlfAUifPYvpX0qZlrcCZ96/6oWxt3QxtK5WVhXz+KsnwW9cIW+3A==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.5.0", - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/typescript-estree": "5.5.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, - "@typescript-eslint/parser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.5.0.tgz", - "integrity": "sha512-JsXBU+kgQOAgzUn2jPrLA+Rd0Y1dswOlX3hp8MuRO1hQDs6xgHtbCXEiAu7bz5hyVURxbXcA2draasMbNqrhmg==", - "dev": true, - "peer": true, - "requires": { - "@typescript-eslint/scope-manager": "5.5.0", - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/typescript-estree": "5.5.0", - "debug": "^4.3.2" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.5.0.tgz", - "integrity": "sha512-0/r656RmRLo7CbN4Mdd+xZyPJ/fPCKhYdU6mnZx+8msAD8nJSP8EyCFkzbd6vNVZzZvWlMYrSNekqGrCBqFQhg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/visitor-keys": "5.5.0" - } - }, - "@typescript-eslint/types": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.5.0.tgz", - "integrity": "sha512-OaYTqkW3GnuHxqsxxJ6KypIKd5Uw7bFiQJZRyNi1jbMJnK3Hc/DR4KwB6KJj6PBRkJJoaNwzMNv9vtTk87JhOg==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.5.0.tgz", - "integrity": "sha512-pVn8btYUiYrjonhMAO0yG8lm7RApzy2L4RC7Td/mC/qFkyf6vRbGyZozoA94+w6D2Y2GRqpMoCWcwx/EUOzyoQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/visitor-keys": "5.5.0", - "debug": "^4.3.2", - "globby": "^11.0.4", - "is-glob": "^4.0.3", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.5.0.tgz", - "integrity": "sha512-4GzJ1kRtsWzHhdM40tv0ZKHNSbkDhF0Woi/TDwVJX6UICwJItvP7ZTXbjTkCdrors7ww0sYe0t+cIKDAJwZ7Kw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.5.0", - "eslint-visitor-keys": "^3.0.0" - } - }, - "acorn": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", - "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-sequence-parser": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz", - "integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "babel-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", - "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", - "dev": true, - "requires": { - "@jest/transform": "^28.1.3", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^28.1.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", - "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", - "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^28.1.3", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", - "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001370", - "electron-to-chromium": "^1.4.202", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.5" - } - }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001384", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001384.tgz", - "integrity": "sha512-BBWt57kqWbc0GYZXb47wTXpmAgqr5LSibPzNjk/AWMdmJMQhLqOl3c/Kd4OAU/tu4NLfYkMx8Tlq3RVBkOBolQ==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "ci-info": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", - "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "detect-gpu": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.5.tgz", - "integrity": "sha512-gKBKx8mj0Fi/8DnjuzxU+aNYF1yWvPxtiOV9o72539wW0HP3ZTJVol/0FUasvdxIY1Bhi19SwIINqXGO+RB/sA==", - "requires": { - "webgl-constants": "^1.1.1" - } - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "electron-to-chromium": { - "version": "1.4.233", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.233.tgz", - "integrity": "sha512-ejwIKXTg1wqbmkcRJh9Ur3hFGHFDZDw1POzdsVrB2WZjgRuRMHIQQKNpe64N/qh3ZtH2otEoRoS+s6arAAuAAw==", - "dev": true - }, - "emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "esbuild": { - "version": "0.16.16", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.16.tgz", - "integrity": "sha512-24JyKq10KXM5EBIgPotYIJ2fInNWVVqflv3gicIyQqfmUqi4HvDW1VR790cBgLJHCl96Syy7lhoz7tLFcmuRmg==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.16.16", - "@esbuild/android-arm64": "0.16.16", - "@esbuild/android-x64": "0.16.16", - "@esbuild/darwin-arm64": "0.16.16", - "@esbuild/darwin-x64": "0.16.16", - "@esbuild/freebsd-arm64": "0.16.16", - "@esbuild/freebsd-x64": "0.16.16", - "@esbuild/linux-arm": "0.16.16", - "@esbuild/linux-arm64": "0.16.16", - "@esbuild/linux-ia32": "0.16.16", - "@esbuild/linux-loong64": "0.16.16", - "@esbuild/linux-mips64el": "0.16.16", - "@esbuild/linux-ppc64": "0.16.16", - "@esbuild/linux-riscv64": "0.16.16", - "@esbuild/linux-s390x": "0.16.16", - "@esbuild/linux-x64": "0.16.16", - "@esbuild/netbsd-x64": "0.16.16", - "@esbuild/openbsd-x64": "0.16.16", - "@esbuild/sunos-x64": "0.16.16", - "@esbuild/win32-arm64": "0.16.16", - "@esbuild/win32-ia32": "0.16.16", - "@esbuild/win32-x64": "0.16.16" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "eslint": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.3.0.tgz", - "integrity": "sha512-aIay56Ph6RxOTC7xyr59Kt3ewX185SaGnAr8eWukoPLeriCrvGjvAubxuvaXOfsxhtwV5g0uBOsyhAom4qJdww==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.0.4", - "@humanwhocodes/config-array": "^0.6.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.0", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.1.0", - "espree": "^9.1.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.2.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint-scope": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", - "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", - "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", - "dev": true - }, - "espree": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.1.0.tgz", - "integrity": "sha512-ZgYLvCS1wxOczBYGcQT9DDWgicXwJ4dbocr9uYN+/eresBAUuBu+O4WzB21ufQ/JqQT8gyp7hJ3z8SHii32mTQ==", - "dev": true, - "requires": { - "acorn": "^8.6.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.1.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expect": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.0.1.tgz", - "integrity": "sha512-yQgemsjLU+1S8t2A7pXT3Sn/v5/37LY8J+tocWtKEA0iEYYc6gfKbbJJX2fxHZmd7K9WpdbQqXUpmYkq1aewYg==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.0.1", - "jest-get-type": "^29.0.0", - "jest-matcher-utils": "^29.0.1", - "jest-message-util": "^29.0.1", - "jest-util": "^29.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "diff-sequences": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.0.0.tgz", - "integrity": "sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-diff": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.0.1.tgz", - "integrity": "sha512-l8PYeq2VhcdxG9tl5cU78ClAlg/N7RtVSp0v3MlXURR0Y99i6eFnegmasOandyTmO6uEdo20+FByAjBFEO9nuw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.0.0", - "jest-get-type": "^29.0.0", - "pretty-format": "^29.0.1" - } - }, - "jest-get-type": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz", - "integrity": "sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==", - "dev": true - }, - "jest-matcher-utils": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.0.1.tgz", - "integrity": "sha512-/e6UbCDmprRQFnl7+uBKqn4G22c/OmwriE5KCMVqxhElKCQUDcFnq5XM9iJeKtzy4DUjxT27y9VHmKPD8BQPaw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.0.1", - "jest-get-type": "^29.0.0", - "pretty-format": "^29.0.1" - } - }, - "jest-message-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.1.tgz", - "integrity": "sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.0.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.0.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", - "dev": true, - "requires": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "pretty-format": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.1.tgz", - "integrity": "sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", - "dev": true - }, - "fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz", - "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "howslow": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/howslow/-/howslow-0.1.0.tgz", - "integrity": "sha512-AD1ERdUseZEi/XyLBa/9LNv4l4GvCCkNT76KpYp0YEv1up8Ow/ZzLy71OtlSdv6b39wxvzJKq9VZLH/83PrQkQ==", - "dev": true - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "ignore": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", - "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", - "requires": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", - "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", - "dev": true, - "requires": { - "@jest/core": "^28.1.3", - "@jest/types": "^28.1.3", - "import-local": "^3.0.2", - "jest-cli": "^28.1.3" - } - }, - "jest-changed-files": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", - "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", - "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "p-limit": "^3.1.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-cli": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", - "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", - "dev": true, - "requires": { - "@jest/core": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-config": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", - "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^28.1.3", - "@jest/types": "^28.1.3", - "babel-jest": "^28.1.3", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^28.1.3", - "jest-environment-node": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", - "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", - "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "jest-util": "^28.1.3", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-node": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", - "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "dev": true - }, - "jest-haste-map": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", - "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-leak-detector": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", - "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - } - } - }, - "jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "requires": { "@babel/code-frame": "^7.12.13", "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "dev": true - }, - "jest-resolve": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", - "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", - "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", - "dev": true, - "requires": { - "jest-regex-util": "^28.0.2", - "jest-snapshot": "^28.1.3" - } - }, - "jest-runner": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", - "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/environment": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "graceful-fs": "^4.2.9", - "jest-docblock": "^28.1.1", - "jest-environment-node": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-leak-detector": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-resolve": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-util": "^28.1.3", - "jest-watcher": "^28.1.3", - "jest-worker": "^28.1.3", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", - "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/globals": "^28.1.3", - "@jest/source-map": "^28.1.2", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", + "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "expect": "^28.1.3", "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "natural-compare": "^1.4.0", + "micromatch": "^4.0.4", "pretty-format": "^28.1.3", - "semver": "^7.3.5" + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, - "dependencies": { - "@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2" - } - }, - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true - }, - "expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "requires": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-util": { + "node_modules/jest-snapshot/node_modules/jest-util": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, - "requires": { + "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", "chalk": "^4.0.0", @@ -12241,64 +4906,89 @@ "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "jest-validate": { + "node_modules/jest-util/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/jest-validate": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", "dev": true, - "requires": { + "dependencies": { "@jest/types": "^28.1.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", @@ -12306,111 +4996,70 @@ "leven": "^3.1.0", "pretty-format": "^28.1.3" }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-watcher": { + "node_modules/jest-validate/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-watcher": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", "dev": true, - "requires": { + "dependencies": { "@jest/test-result": "^28.1.3", "@jest/types": "^28.1.3", "@types/node": "*", @@ -12420,1272 +5069,1723 @@ "jest-util": "^28.1.3", "string-length": "^4.0.1" }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watcher/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-worker": { + "node_modules/jest-worker": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", "dev": true, - "requires": { + "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "js-sha256": { + "node_modules/js-sha256": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" }, - "js-tokens": { + "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "js-yaml": { + "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "requires": { + "dependencies": { "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "jsesc": { + "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, - "json-parse-even-better-errors": { + "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "json-schema-traverse": { + "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "json-stable-stringify-without-jsonify": { + "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "json5": { + "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true }, - "jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jstat": { + "version": "1.9.6", + "resolved": "https://registry.npmjs.org/jstat/-/jstat-1.9.6.tgz", + "integrity": "sha512-rPBkJbK2TnA8pzs93QcDDPlKcrtZWuuCo2dVR0TFLOJSxhqfWOVCSp8aV3/oSbn+4uY4yw1URtLpHQedtmXfug==", "dev": true }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" + "dependencies": { + "json-buffer": "3.0.1" } }, - "kleur": { + "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "leven": { + "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "levn": { + "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "requires": { + "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" } }, - "lines-and-columns": { + "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "requires": { - "p-locate": "^4.1.0" + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "lodash.memoize": { + "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, - "lodash.merge": { + "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "requires": { - "yallist": "^4.0.0" + "dependencies": { + "yallist": "^3.0.2" } }, - "lunr": { + "node_modules/lunr": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", "dev": true }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, - "requires": { - "semver": "^6.0.0" + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "make-error": { + "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "makeerror": { + "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, - "requires": { + "dependencies": { "tmpl": "1.0.5" } }, - "marked": { + "node_modules/marked": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "dev": true + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } }, - "merge-stream": { + "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, - "merge2": { + "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "dev": true, + "engines": { + "node": ">= 8" + } }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" } }, - "mimic-fn": { + "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "minimatch": { + "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "requires": { + "dependencies": { "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "ms": { + "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "natural-compare": { + "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, - "neo-async": { + "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node-int64": { + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, - "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, - "normalize-path": { + "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "npm-run-path": { + "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, - "requires": { + "dependencies": { "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "requires": { + "dependencies": { "wrappy": "1" } }, - "onetime": { + "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, - "requires": { + "dependencies": { "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, - "requires": { + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" } }, - "p-limit": { + "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "requires": { + "dependencies": { "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "requires": { - "p-limit": "^2.2.0" - }, "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "p-try": { + "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "parent-module": { + "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "requires": { + "dependencies": { "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "parse-json": { + "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, - "requires": { + "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "path-exists": { + "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "path-is-absolute": { + "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "path-key": { + "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "path-parse": { + "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } }, - "picomatch": { + "node_modules/pkg-dir/node_modules/p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "node_modules/playwright": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", + "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", "dev": true, - "requires": { - "find-up": "^4.0.0" + "dependencies": { + "playwright-core": "1.39.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" } }, - "playwright-core": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.25.2.tgz", - "integrity": "sha512-0yTbUE9lIddkEpLHL3u8PoCL+pWiZtj5A/j3U7YoNjcmKKDGBnCrgHJMzwd2J5vy6l28q4ki3JIuz7McLHhl1A==", - "dev": true + "node_modules/playwright-core": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } }, - "prelude-ls": { + "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.8.0" + } }, - "prettier": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", - "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", - "dev": true + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } }, - "pretty-format": { + "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, - "requires": { + "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, - "prompts": { + "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, - "requires": { + "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } }, - "queue-microtask": { + "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "react-is": { + "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, - "reflect-metadata": { + "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "replace-in-file": { + "node_modules/replace-in-file": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.3.5.tgz", "integrity": "sha512-arB9d3ENdKva2fxRnSjwBEXfK1npgyci7ZZuwysgAp7ORjHSyxz6oqIjTEv8R0Ydl4Ll7uOAZXL4vbkhGIizCg==", "dev": true, - "requires": { + "dependencies": { "chalk": "^4.1.2", "glob": "^7.2.0", "yargs": "^17.2.1" }, + "bin": { + "replace-in-file": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/replace-in-file/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "require-directory": { + "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, - "requires": { - "is-core-module": "^2.9.0", + "dependencies": { + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "resolve-cwd": { + "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, - "requires": { + "dependencies": { "resolve-from": "^5.0.0" }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" } }, - "resolve-from": { + "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true + "node_modules/resolve.exports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", + "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", + "dev": true, + "engines": { + "node": ">=10" + } }, - "reusify": { + "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } }, - "rimraf": { + "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, - "requires": { + "dependencies": { "glob": "^7.1.3" }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "run-parallel": { + "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, - "requires": { + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { "queue-microtask": "^1.2.2" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "shebang-command": { + "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "requires": { + "dependencies": { "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "shebang-regex": { + "node_modules/shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "shiki": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.1.tgz", - "integrity": "sha512-+Jz4nBkCBe0mEDqo1eKRcCdjRtrCjozmcbTUjbPTX7OOJfEbTZzlUWlZtGe3Gb5oV1/jnojhG//YZc3rs9zSEw==", + "node_modules/shiki": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", + "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", "dev": true, - "requires": { + "dependencies": { "ansi-sequence-parser": "^1.1.0", "jsonc-parser": "^3.2.0", "vscode-oniguruma": "^1.7.0", "vscode-textmate": "^8.0.0" } }, - "signal-exit": { + "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "sisteransi": { + "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true }, - "slash": { + "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "source-map": { + "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "source-map-support": { + "node_modules/source-map-support": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, - "requires": { + "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, - "sprintf-js": { + "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, - "requires": { + "dependencies": { "escape-string-regexp": "^2.0.0" }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" } }, - "string-length": { + "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, - "requires": { + "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" } }, - "string-width": { + "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "requires": { + "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-ansi": { + "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "requires": { + "dependencies": { "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-bom": { + "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "strip-final-newline": { + "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "strip-json-comments": { + "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "has-flag": "^3.0.0" + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", "dev": true, - "requires": { + "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "engines": { + "node": ">=8" } }, - "supports-preserve-symlinks-flag": { + "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "terminal-link": { + "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", "dev": true, - "requires": { + "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "test-exclude": { + "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, - "requires": { + "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "text-table": { + "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "tmpl": { + "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "to-fast-properties": { + "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-jest": { + "version": "28.0.8", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", + "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^28.0.0", + "json5": "^2.2.1", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^28.0.0", + "babel-jest": "^28.0.0", + "jest": "^28.0.0", + "typescript": ">=4.3" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/ts-jest/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, - "requires": { - "is-number": "^7.0.0" + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "ts-jest": { - "version": "28.0.8", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", - "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, - "requires": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^28.0.0", - "json5": "^2.2.1", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, - "type-check": { + "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "requires": { + "dependencies": { "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" } }, - "type-detect": { + "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "typedoc": { - "version": "0.24.6", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.6.tgz", - "integrity": "sha512-c3y3h45xJv3qYwKDAwU6Cl+26CjT0ZvblHzfHJ+SjQDM4p1mZxtgHky4lhmG0+nNarRht8kADfZlbspJWdZarQ==", + "node_modules/typedoc": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz", + "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==", "dev": true, - "requires": { + "dependencies": { "lunr": "^2.3.9", "marked": "^4.3.0", "minimatch": "^9.0.0", "shiki": "^0.14.1" }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", - "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 14.14" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x" } }, - "typedoc-plugin-markdown": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.15.3.tgz", - "integrity": "sha512-idntFYu3vfaY3eaD+w9DeRd0PmNGqGuNLKihPU9poxFGnATJYGn9dPtEhn2QrTdishFMg7jPXAhos+2T6YCWRQ==", + "node_modules/typedoc-plugin-markdown": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.17.1.tgz", + "integrity": "sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==", "dev": true, - "requires": { + "dependencies": { "handlebars": "^4.7.7" + }, + "peerDependencies": { + "typedoc": ">=0.24.0" } }, - "typedoc-plugin-merge-modules": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/typedoc-plugin-merge-modules/-/typedoc-plugin-merge-modules-5.0.1.tgz", - "integrity": "sha512-7fiMYDUaeslsGSFDevw+azhD0dFJce0h2g5UuQ8zXljoky+YfmzoNkoTCx+KWaNJo6rz2DzaD2feVJyUhvUegg==", + "node_modules/typedoc-plugin-merge-modules": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-merge-modules/-/typedoc-plugin-merge-modules-5.1.0.tgz", + "integrity": "sha512-jXH27L/wlxFjErgBXleh3opVgjVTXFEuBo68Yfl18S9Oh/IqxK6NV94jlEJ9hl4TXc9Zm2l7Rfk41CEkcCyvFQ==", "dev": true, - "requires": {} + "peerDependencies": { + "typedoc": "0.24.x || 0.25.x" + } }, - "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } }, - "uglify-js": { + "node_modules/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "dev": true, - "optional": true + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, - "update-browserslist-db": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", - "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, - "requires": { + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "uri-js": { + "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "requires": { + "dependencies": { "punycode": "^2.1.0" } }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "node_modules/v8-to-istanbul": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", + "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", "dev": true, - "requires": { + "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" } }, - "vscode-oniguruma": { + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", "dev": true }, - "vscode-textmate": { + "node_modules/vscode-textmate": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", "dev": true }, - "walker": { + "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, - "requires": { + "dependencies": { "makeerror": "1.0.12" } }, - "webgl-constants": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", - "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, - "whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + "node_modules/whatwg-fetch": { + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz", + "integrity": "sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } }, - "which": { + "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "requires": { + "dependencies": { "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wordwrap": { + "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, - "wrap-ansi": { + "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "write-file-atomic": { + "node_modules/write-file-atomic": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, - "requires": { + "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "y18n": { + "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + } }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, - "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "requires": { - "cliui": "^7.0.2", + "dependencies": { + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, - "yargs-parser": { + "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true + "dev": true, + "engines": { + "node": ">=12" + } }, - "yocto-queue": { + "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 5d5c9d157d..93fdc88923 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,11 @@ { - "name": "snarkyjs", + "name": "o1js", "description": "TypeScript framework for zk-SNARKs and zkApps", - "version": "0.12.1", + "version": "0.17.0", "license": "Apache-2.0", - "homepage": "https://github.com/o1-labs/snarkyjs/", + "homepage": "https://github.com/o1-labs/o1js/", "keywords": [ "mina", - "snarkyjs", "zkapp", "zk", "smart contract", @@ -44,58 +43,60 @@ }, "scripts": { "dev": "npx tsc -p tsconfig.test.json && node src/build/copy-to-dist.js", - "build": "node src/build/copy-artifacts.js && rimraf ./dist/node && npm run dev && node src/build/buildNode.js", - "build:bindings": "./src/bindings/scripts/build-snarkyjs-node.sh", - "build:update-bindings": "./src/bindings/scripts/update-snarkyjs-bindings.sh", + "build": "node src/build/copy-artifacts.js && rimraf ./dist/node && npm run dev && node src/build/build-node.js", + "build:bindings": "./src/bindings/scripts/build-o1js-node.sh", + "build:update-bindings": "./src/bindings/scripts/update-o1js-bindings.sh", "build:wasm": "./src/bindings/scripts/update-wasm-and-types.sh", - "build:web": "rimraf ./dist/web && node src/build/buildWeb.js", - "build:examples": "rimraf ./dist/examples && npx tsc -p tsconfig.examples.json || exit 0", - "build:docs": "npx typedoc", - "serve:web": "cp src/bindings/compiled/web_bindings/server.js src/bindings/compiled/web_bindings/index.html src/examples/simple_zkapp.js dist/web && node dist/web/server.js", - "prepublish:web": "NODE_ENV=production node src/build/buildWeb.js", - "prepublish:node": "npm run build && NODE_ENV=production node src/build/buildNode.js", - "prepublish:both": "npm run prepublish:web && npm run prepublish:node", + "build:web": "rimraf ./dist/web && node src/build/build-web.js", + "build:examples": "npm run build && rimraf ./dist/examples && npx tsc -p tsconfig.examples.json && npx tsc -p benchmark/tsconfig.json", + "build:docs": "npx typedoc --tsconfig ./tsconfig.web.json", + "prepublish:web": "NODE_ENV=production node src/build/build-web.js", + "prepublish:node": "node src/build/copy-artifacts.js && rimraf ./dist/node && npx tsc -p tsconfig.node.json && node src/build/copy-to-dist.js && NODE_ENV=production node src/build/build-node.js", "prepublishOnly": "npm run prepublish:web && npm run prepublish:node", - "bootstrap": "npm run build && node src/build/extractJsooMethods.cjs && npm run build", + "dump-vks": "npm run build && ./run tests/vk-regression/vk-regression.ts --bundle --dump", "format": "prettier --write --ignore-unknown **/*", - "test": "./run-jest-tests.sh", "clean": "rimraf ./dist && rimraf ./src/bindings/compiled/_node_bindings", "clean-all": "npm run clean && rimraf ./tests/report && rimraf ./tests/test-artifacts", + "test": "./run-jest-tests.sh", "test:integration": "./run-integration-tests.sh", "test:unit": "./run-unit-tests.sh", "test:e2e": "rimraf ./tests/report && rimraf ./tests/test-artifacts && npx playwright test", - "e2e:prepare-server": "npm run build:examples && (cp -rf dist/examples dist/web || :) && node src/build/e2eTestsBuildHelper.js && cp -rf src/bindings/compiled/web_bindings/index.html src/bindings/compiled/web_bindings/server.js tests/artifacts/html/*.html tests/artifacts/javascript/*.js dist/web", + "e2e:prepare-server": "npm run build:examples && (cp -rf dist/examples dist/web || :) && node src/build/e2e-tests-build-helper.js && cp -rf src/examples/plain-html/index.html src/examples/plain-html/server.js tests/artifacts/html/*.html tests/artifacts/javascript/*.js dist/web", "e2e:run-server": "node dist/web/server.js", "e2e:install": "npx playwright install --with-deps", - "e2e:show-report": "npx playwright show-report tests/report" + "e2e:show-report": "npx playwright show-report tests/report", + "update-changelog": "./update-changelog.sh" }, "author": "O(1) Labs", "devDependencies": { + "@influxdata/influxdb-client": "^1.33.2", + "@noble/hashes": "^1.3.2", "@playwright/test": "^1.25.2", "@types/isomorphic-fetch": "^0.0.36", "@types/jest": "^27.0.0", "@types/node": "^18.14.2", "@typescript-eslint/eslint-plugin": "^5.0.0", - "esbuild": "^0.16.16", + "esbuild": "^0.19.2", "eslint": "^8.0.0", "expect": "^29.0.1", "fs-extra": "^10.0.0", "glob": "^8.0.3", "howslow": "^0.1.0", "jest": "^28.1.3", + "jstat": "^1.9.6", "minimist": "^1.2.7", "prettier": "^2.8.4", "replace-in-file": "^6.3.5", "rimraf": "^3.0.2", "ts-jest": "^28.0.8", - "typedoc": "^0.24.6", + "typedoc": "^0.24.8", "typedoc-plugin-markdown": "^3.15.3", "typedoc-plugin-merge-modules": "^5.0.1", - "typescript": "^4.9.5" + "typescript": "5.1" }, "dependencies": { "blakejs": "1.2.1", - "detect-gpu": "^5.0.5", + "cachedir": "^2.4.0", "isomorphic-fetch": "^3.0.0", "js-sha256": "^0.9.0", "reflect-metadata": "^0.1.13", diff --git a/run-ci-benchmarks.sh b/run-ci-benchmarks.sh new file mode 100644 index 0000000000..927df47fab --- /dev/null +++ b/run-ci-benchmarks.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -e + +echo "" +echo "Running o1js benchmarks." +echo "" + +./run benchmark/runners/with-cloud-history.ts --bundle >>benchmarks.log 2>&1 diff --git a/run-ci-live-tests.sh b/run-ci-live-tests.sh new file mode 100755 index 0000000000..3d6a53de9c --- /dev/null +++ b/run-ci-live-tests.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set -o pipefail + +# Function to add a prefix to each direct line of output +add_prefix() { + local prefix=$1 + while IFS= read -r line; do + echo "$prefix : $line" + done +} + +echo "" +echo "Running integration tests against the real Mina network." +echo "" + +./run src/examples/zkapps/hello-world/run-live.ts --bundle | add_prefix "HELLO_WORLD" & +HELLO_WORLD_PROC=$! +./run src/examples/zkapps/dex/run-live.ts --bundle | add_prefix "DEX" & +DEX_PROC=$! +./run src/examples/fetch-live.ts --bundle | add_prefix "FETCH" & +FETCH_PROC=$! +./run src/tests/transaction-flow.ts --bundle | add_prefix "TRANSACTION_FLOW" & +TRANSACTION_FLOW_PROC=$! + +# Wait for each process and capture their exit statuses +FAILURE=0 +wait $HELLO_WORLD_PROC +if [ $? -ne 0 ]; then + echo "" + echo "HELLO_WORLD test failed." + echo "" + FAILURE=1 +fi +wait $DEX_PROC +if [ $? -ne 0 ]; then + echo "" + echo "DEX test failed." + echo "" + FAILURE=1 +fi +wait $FETCH_PROC +if [ $? -ne 0 ]; then + echo "" + echo "FETCH test failed." + echo "" + FAILURE=1 +fi +wait $TRANSACTION_FLOW_PROC +if [ $? -ne 0 ]; then + echo "" + echo "TRANSACTION_FLOW test failed." + echo "" + FAILURE=1 +fi + +# Exit with failure if any process failed +if [ $FAILURE -ne 0 ]; then + exit 1 +fi + +echo "" +echo "All tests completed successfully." +echo "" diff --git a/run-ci-tests.sh b/run-ci-tests.sh index dc9de7c215..dd52223ea6 100755 --- a/run-ci-tests.sh +++ b/run-ci-tests.sh @@ -2,47 +2,53 @@ set -e case $TEST_TYPE in - "Simple integration tests" ) - echo "Running basic integration tests"; - ./run src/examples/zkapps/hello_world/run.ts --bundle - ./run src/examples/simple_zkapp.ts --bundle - ./run src/examples/zkapps/reducer/reducer_composite.ts --bundle - ./run src/examples/zkapps/composability.ts --bundle ;; - - "Voting integration tests" ) - echo "Running voting integration tests"; - ./run src/examples/zkapps/voting/run.ts --bundle ;; - - "DEX integration tests" ) - echo "Running DEX integration tests"; - ./run src/examples/zkapps/dex/run.ts --bundle - ./run src/examples/zkapps/dex/upgradability.ts --bundle ;; - - "DEX integration test with proofs" ) - echo "Running DEX integration test with proofs"; - ./run src/examples/zkapps/dex/happy-path-with-proofs.ts --bundle ;; - - "Berkeley Live" ) - echo "Running Berkeley Live integration tests"; - ./run src/examples/zkapps/hello_world/run_berkeley.ts --bundle ;; - - "Unit tests" ) - echo "Running unit tests"; - cd src/mina-signer - npm run build - cd ../.. - npm run test:unit - npm run test - ;; - - "Verification Key Regression Check" ) - echo "Running Regression checks" - ./run ./src/examples/vk_regression.ts --bundle ;; - - "CommonJS test" ) - echo "Testing CommonJS version"; - node src/examples/commonjs.cjs - ;; - - * ) echo "ERROR: Invalid enviroment variable, not clear what tests to run! $CI_NODE_INDEX"; exit 1 ;; +"Simple integration tests") + echo "Running basic integration tests" + ./run src/examples/zkapps/hello-world/run.ts --bundle + ./run src/examples/simple-zkapp.ts --bundle + ./run src/examples/zkapps/reducer/reducer-composite.ts --bundle + ./run src/examples/zkapps/composability.ts --bundle + ./run src/tests/fake-proof.ts + ./run tests/vk-regression/diverse-zk-program-run.ts --bundle + ;; + +"Voting integration tests") + echo "Running voting integration tests" + ./run src/examples/zkapps/voting/run.ts --bundle + ;; + +"DEX integration tests") + echo "Running DEX integration tests" + ./run src/examples/zkapps/dex/run.ts --bundle + ./run src/examples/zkapps/dex/upgradability.ts --bundle + ;; + +"DEX integration test with proofs") + echo "Running DEX integration test with proofs" + ./run src/examples/zkapps/dex/happy-path-with-proofs.ts --bundle + ;; + +"Unit tests") + echo "Running unit tests" + cd src/mina-signer + npm run build + cd ../.. + npm run test:unit + npm run test + ;; + +"Verification Key Regression Check") + echo "Running Regression checks" + ./run ./tests/vk-regression/vk-regression.ts --bundle + ;; + +"CommonJS test") + echo "Testing CommonJS version" + node src/examples/commonjs.cjs + ;; + +*) + echo "ERROR: Invalid environment variable, not clear what tests to run! $CI_NODE_INDEX" + exit 1 + ;; esac diff --git a/run-debug b/run-debug new file mode 100755 index 0000000000..05642ff1c3 --- /dev/null +++ b/run-debug @@ -0,0 +1 @@ +node --inspect-brk --enable-source-maps src/build/run.js $@ diff --git a/run-in-browser.js b/run-in-browser.js index 030118976a..70c0ca3534 100755 --- a/run-in-browser.js +++ b/run-in-browser.js @@ -3,7 +3,7 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import http from 'node:http'; import minimist from 'minimist'; -import { build } from './src/build/buildExample.js'; +import { build } from './src/build/build-example.js'; let { _: [filePath], @@ -32,7 +32,7 @@ const indexHtml = ` - snarkyjs + o1js diff --git a/run-integration-tests.sh b/run-integration-tests.sh index 129f2f2145..3ffbbe4cac 100755 --- a/run-integration-tests.sh +++ b/run-integration-tests.sh @@ -1,11 +1,11 @@ #!/bin/bash set -e -./run src/examples/zkapps/hello_world/run.ts --bundle +./run src/examples/zkapps/hello-world/run.ts --bundle ./run src/examples/zkapps/voting/run.ts --bundle -./run src/examples/simple_zkapp.ts -./run src/examples/zkapps/reducer/reducer_composite.ts -./run src/examples/zkapps/composability.ts +./run src/examples/simple-zkapp.ts --bundle +./run src/examples/zkapps/reducer/reducer-composite.ts --bundle +./run src/examples/zkapps/composability.ts --bundle ./run src/examples/zkapps/dex/run.ts --bundle -./run src/examples/zkapps/dex/happy-path-with-proofs.ts --bundle +./run src/examples/zkapps/dex/happy-path-with-actions.ts --bundle ./run src/examples/zkapps/dex/upgradability.ts --bundle diff --git a/run-mina-integration-tests.sh b/run-mina-integration-tests.sh deleted file mode 100755 index 2ed8854fd7..0000000000 --- a/run-mina-integration-tests.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -set -e - -node tests/integration/simple-zkapp-mock-apply.js -node tests/integration/inductive-proofs.js diff --git a/run-minimal-mina-tests.sh b/run-minimal-mina-tests.sh index 3aae238cce..d6380585f2 100755 --- a/run-minimal-mina-tests.sh +++ b/run-minimal-mina-tests.sh @@ -1,4 +1,6 @@ #!/bin/bash set -e +npm run dev + ./run src/tests/inductive-proofs-small.ts --bundle diff --git a/src/bindings b/src/bindings index 5ed3f47ab9..3bc828d876 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 5ed3f47ab9446422280aa04fe748bc72828bc0f1 +Subproject commit 3bc828d876406b0d8b78f7cb548cc165d920fbf9 diff --git a/src/build/buildExample.js b/src/build/build-example.js similarity index 73% rename from src/build/buildExample.js rename to src/build/build-example.js index a7f75c3038..ffde281aa4 100644 --- a/src/build/buildExample.js +++ b/src/build/build-example.js @@ -2,6 +2,7 @@ import fs from 'fs/promises'; import path from 'path'; import ts from 'typescript'; import esbuild from 'esbuild'; +import { platform } from 'node:process'; export { buildAndImport, build, buildOne }; @@ -11,13 +12,16 @@ async function buildAndImport(srcPath, { keepFile = false }) { try { importedModule = await import(absPath); } finally { - if (!keepFile) await fs.unlink(absPath); + if (!keepFile) await fs.unlink(absPath.replace(/^file:\/\/\/*/, '')); } return importedModule; } async function build(srcPath, isWeb = false) { let tsConfig = findTsConfig() ?? defaultTsConfig; + // TODO hack because ts.transpileModule doesn't treat module = 'nodenext' correctly + // but `tsc` demands it to be `nodenext` + tsConfig.compilerOptions.module = 'esnext'; let outfile = srcPath.replace('.ts', '.tmp.js'); @@ -31,20 +35,27 @@ async function build(srcPath, isWeb = false) { resolveExtensions: ['.node.js', '.ts', '.js'], logLevel: 'error', plugins: isWeb - ? [typescriptPlugin(tsConfig)] + ? [typescriptPlugin(tsConfig), makeO1jsExternal()] : [ typescriptPlugin(tsConfig), makeNodeModulesExternal(), makeJsooExternal(), ], + dropLabels: ['CJS'], }); let absPath = path.resolve('.', outfile); + if (platform === 'win32') { + absPath = 'file:///' + absPath; + } return absPath; } async function buildOne(srcPath) { let tsConfig = findTsConfig() ?? defaultTsConfig; + // TODO hack because ts.transpileModule doesn't treat module = 'nodenext' correctly + // but `tsc` demands it to be `nodenext` + tsConfig.compilerOptions.module = 'esnext'; let outfile = path.resolve( './dist/node', @@ -63,6 +74,9 @@ async function buildOne(srcPath) { }); let absPath = path.resolve('.', outfile); + if (platform === 'win32') { + absPath = 'file:///' + absPath; + } return absPath; } @@ -73,7 +87,7 @@ const defaultTsConfig = { target: 'esnext', importHelpers: true, strict: true, - moduleResolution: 'node', + moduleResolution: 'nodenext', esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, @@ -97,12 +111,24 @@ function typescriptPlugin(tsConfig) { } function makeNodeModulesExternal() { - let isNodeModule = /^[^./]|^\.[^./]|^\.\.[^/]/; + let isNodeModule = /^[^./\\]|^\.[^./\\]|^\.\.[^/\\]/; return { name: 'plugin-external', setup(build) { build.onResolve({ filter: isNodeModule }, ({ path }) => ({ path, + external: !(platform === 'win32' && path.endsWith('index.js')), + })); + }, + }; +} + +function makeO1jsExternal() { + return { + name: 'plugin-external', + setup(build) { + build.onResolve({ filter: /^o1js$/ }, () => ({ + path: './index.js', external: true, })); }, @@ -110,7 +136,7 @@ function makeNodeModulesExternal() { } function makeJsooExternal() { - let isJsoo = /(bc.cjs|plonk_wasm.cjs|wrapper.js)$/; + let isJsoo = /(bc.cjs|plonk_wasm.cjs)$/; return { name: 'plugin-external', setup(build) { diff --git a/src/build/buildNode.js b/src/build/build-node.js similarity index 90% rename from src/build/buildNode.js rename to src/build/build-node.js index 6d002f64d3..4ba289033b 100644 --- a/src/build/buildNode.js +++ b/src/build/build-node.js @@ -1,6 +1,6 @@ import path from 'node:path'; +import { platform } from 'node:process'; import { fileURLToPath } from 'node:url'; -import { exec } from 'node:child_process'; import esbuild from 'esbuild'; import minimist from 'minimist'; @@ -41,18 +41,19 @@ async function buildNode({ production }) { resolveExtensions: ['.node.js', '.ts', '.js'], allowOverwrite: true, plugins: [makeNodeModulesExternal(), makeJsooExternal()], + dropLabels: ['ESM'], minify: false, }); } function makeNodeModulesExternal() { - let isNodeModule = /^[^./]|^\.[^./]|^\.\.[^/]/; + let isNodeModule = /^[^./\\]|^\.[^./\\]|^\.\.[^/\\]/; return { name: 'plugin-external', setup(build) { build.onResolve({ filter: isNodeModule }, ({ path }) => ({ path, - external: true, + external: !(platform === 'win32' && path.endsWith('index.js')), })); }, }; diff --git a/src/build/buildWeb.js b/src/build/build-web.js similarity index 92% rename from src/build/buildWeb.js rename to src/build/build-web.js index 2b07ac2ee3..0ec36a7da8 100644 --- a/src/build/buildWeb.js +++ b/src/build/build-web.js @@ -46,27 +46,25 @@ async function buildWeb({ production }) { await writeFile(tmpBindingsPath, bindings); // run typescript - let tscPromise = execPromise('npx tsc -p tsconfig.web.json'); + await execPromise('npx tsc -p tsconfig.web.json'); // copy over pure js files - let copyPromise = copy({ + await copy({ './src/bindings/compiled/web_bindings/': './dist/web/web_bindings/', './src/snarky.d.ts': './dist/web/snarky.d.ts', - './src/bindings/js/wrapper.web.js': './dist/web/bindings/js/wrapper.js', + './src/snarky.web.js': './dist/web/snarky.js', './src/bindings/js/web/': './dist/web/bindings/js/web/', }); - await Promise.all([tscPromise, copyPromise]); - if (minify) { - let snarkyJsWebPath = './dist/web/web_bindings/snarky_js_web.bc.js'; - let snarkyJsWeb = await readFile(snarkyJsWebPath, 'utf8'); - let { code } = await esbuild.transform(snarkyJsWeb, { + let o1jsWebPath = './dist/web/web_bindings/o1js_web.bc.js'; + let o1jsWeb = await readFile(o1jsWebPath, 'utf8'); + let { code } = await esbuild.transform(o1jsWeb, { target, logLevel: 'error', minify, }); - await writeFile(snarkyJsWebPath, code); + await writeFile(o1jsWebPath, code); } // overwrite plonk_wasm with bundled version @@ -90,6 +88,7 @@ async function buildWeb({ production }) { outfile: 'dist/web/index.js', resolveExtensions: ['.js', '.ts'], plugins: [wasmPlugin(), srcStringPlugin()], + dropLabels: ['CJS'], external: ['*.bc.js'], target, allowOverwrite: true, @@ -137,7 +136,7 @@ function rewriteBundledWasmBindings(src) { let exportSlice = src.slice(i); let defaultExport = exportSlice.match(/\w* as default/)[0]; exportSlice = exportSlice - .replace(defaultExport, `default: init`) + .replace(defaultExport, `default: __wbg_init`) .replace('export', 'return'); src = src.slice(0, i) + exportSlice; diff --git a/src/build/copy-to-dist.js b/src/build/copy-to-dist.js index 96bc96937b..6218414713 100644 --- a/src/build/copy-to-dist.js +++ b/src/build/copy-to-dist.js @@ -2,7 +2,11 @@ import { copyFromTo } from './utils.js'; await copyFromTo( - ['src/snarky.d.ts', 'src/bindings/compiled/_node_bindings'], + [ + 'src/snarky.d.ts', + 'src/bindings/compiled/_node_bindings', + 'src/bindings/compiled/node_bindings/plonk_wasm.d.cts', + ], 'src/', 'dist/node/' ); diff --git a/src/build/e2eTestsBuildHelper.js b/src/build/e2e-tests-build-helper.js similarity index 91% rename from src/build/e2eTestsBuildHelper.js rename to src/build/e2e-tests-build-helper.js index 6674e89dca..3709c514e8 100644 --- a/src/build/e2eTestsBuildHelper.js +++ b/src/build/e2e-tests-build-helper.js @@ -2,7 +2,7 @@ import replace from 'replace-in-file'; const options = { files: './dist/web/examples/zkapps/**/*.js', - from: /from 'snarkyjs'/g, + from: /from 'o1js'/g, to: "from '../../../index.js'", }; diff --git a/src/build/jsLayoutToTypes.mjs b/src/build/js-layout-to-types.mjs similarity index 81% rename from src/build/jsLayoutToTypes.mjs rename to src/build/js-layout-to-types.mjs index 2f0b87caef..70c983d0f0 100644 --- a/src/build/jsLayoutToTypes.mjs +++ b/src/build/js-layout-to-types.mjs @@ -9,7 +9,7 @@ let selfPath = fileURLToPath(import.meta.url); let jsonPath = path.resolve(selfPath, '../../bindings/ocaml/jsLayout.json'); let jsLayout = JSON.parse(await fs.readFile(jsonPath, 'utf8')); -console.log(`jsLayoutToTypes.mjs: generating TS types from ${jsonPath}`); +console.log(`js-layout-to-types.mjs: generating TS types from ${jsonPath}`); let builtinLeafTypes = new Set([ 'number', @@ -106,11 +106,22 @@ function writeType(typeData, isJson, withTypeMap) { }; } -function writeTsContent(types, isJson, leavesRelPath) { +function writeTsContent({ + jsLayout: types, + isJson, + isProvable, + leavesRelPath, +}) { let output = ''; let dependencies = new Set(); let converters = {}; let exports = new Set(isJson ? [] : ['customTypes']); + + let fromLayout = isProvable ? 'provableFromLayout' : 'signableFromLayout'; + let FromLayout = isProvable ? 'ProvableFromLayout' : 'SignableFromLayout'; + let GenericType = isProvable ? 'GenericProvableExtended' : 'GenericSignable'; + let GeneratedType = isProvable ? 'ProvableExtended' : 'Signable'; + for (let [Type, value] of Object.entries(types)) { let inner = writeType(value, isJson); exports.add(Type); @@ -118,7 +129,7 @@ function writeTsContent(types, isJson, leavesRelPath) { mergeObject(converters, inner.converters); output += `type ${Type} = ${inner.output};\n\n`; if (!isJson) { - output += `let ${Type} = provableFromLayout<${Type}, Json.${Type}>(jsLayout.${Type} as any);\n\n`; + output += `let ${Type} = ${fromLayout}<${Type}, Json.${Type}>(jsLayout.${Type} as any);\n\n`; } } @@ -135,8 +146,8 @@ function writeTsContent(types, isJson, leavesRelPath) { import { ${[...imports].join(', ')} } from '${importPath}'; ${ !isJson - ? "import { GenericProvableExtended } from '../../lib/generic.js';\n" + - "import { ProvableFromLayout, GenericLayout } from '../../lib/from-layout.js';\n" + + ? `import { ${GenericType} } from '../../lib/generic.js';\n` + + `import { ${FromLayout}, GenericLayout } from '../../lib/from-layout.js';\n` + "import * as Json from './transaction-json.js';\n" + "import { jsLayout } from './js-layout.js';\n" : '' @@ -147,7 +158,7 @@ ${ !isJson ? 'export { Json };\n' + `export * from '${leavesRelPath}';\n` + - 'export { provableFromLayout, toJSONEssential, emptyValue, Layout, TypeMap };\n' + `export { ${fromLayout}, toJSONEssential, empty, Layout, TypeMap };\n` : `export * from '${leavesRelPath}';\n` + 'export { TypeMap };\n' } @@ -158,7 +169,7 @@ ${ (!isJson || '') && ` const TypeMap: { - [K in keyof TypeMap]: ProvableExtended; + [K in keyof TypeMap]: ${GeneratedType}; } = { ${[...typeMapKeys].join(', ')} } @@ -168,14 +179,14 @@ const TypeMap: { ${ (!isJson || '') && ` -type ProvableExtended = GenericProvableExtended; +type ${GeneratedType} = ${GenericType}; type Layout = GenericLayout; type CustomTypes = { ${customTypes - .map((c) => `${c.typeName}: ProvableExtended<${c.type}, ${c.jsonType}>;`) + .map((c) => `${c.typeName}: ${GeneratedType}<${c.type}, ${c.jsonType}>;`) .join(' ')} } let customTypes: CustomTypes = { ${customTypeNames.join(', ')} }; -let { provableFromLayout, toJSONEssential, emptyValue } = ProvableFromLayout< +let { ${fromLayout}, toJSONEssential, empty } = ${FromLayout}< TypeMap, Json.TypeMap >(TypeMap, customTypes); @@ -196,25 +207,27 @@ async function writeTsFile(content, relPath) { let genPath = '../../bindings/mina-transaction/gen'; await ensureDir(genPath); -let jsonTypesContent = writeTsContent( +let jsonTypesContent = writeTsContent({ jsLayout, - true, - '../transaction-leaves-json.js' -); + isJson: true, + leavesRelPath: '../transaction-leaves-json.js', +}); await writeTsFile(jsonTypesContent, `${genPath}/transaction-json.ts`); -let jsTypesContent = writeTsContent( +let jsTypesContent = writeTsContent({ jsLayout, - false, - '../transaction-leaves.js' -); + isJson: false, + isProvable: true, + leavesRelPath: '../transaction-leaves.js', +}); await writeTsFile(jsTypesContent, `${genPath}/transaction.ts`); -jsTypesContent = writeTsContent( +jsTypesContent = writeTsContent({ jsLayout, - false, - '../transaction-leaves-bigint.js' -); + isJson: false, + isProvable: false, + leavesRelPath: '../transaction-leaves-bigint.js', +}); await writeTsFile(jsTypesContent, `${genPath}/transaction-bigint.ts`); await writeTsFile( diff --git a/src/build/run.js b/src/build/run.js index 51c89c8852..7902f1ccdf 100755 --- a/src/build/run.js +++ b/src/build/run.js @@ -1,6 +1,6 @@ #!/usr/bin/env node import minimist from 'minimist'; -import { buildAndImport, buildOne } from './buildExample.js'; +import { buildAndImport, buildOne } from './build-example.js'; let { _: [filePath], diff --git a/src/examples/README.md b/src/examples/README.md new file mode 100644 index 0000000000..24add4d6b8 --- /dev/null +++ b/src/examples/README.md @@ -0,0 +1,25 @@ +# o1js Examples + +This folder contains many examples for using o1js. Take a look around! + +## Running examples + +You can run most examples using Node.js from the root directory, using the `./run` script: + +``` +./run src/examples/some-example.ts +``` + +Some examples depend on other files in addition to `"o1js"`. For those examples, you need to add the `--bundle` option to bundle them before running: + +``` +./run src/examples/multi-file-example.ts --bundle +``` + +Most of the examples do not depend on Node.js specific APIs, and can also be run in a browser. To do so, use: + +``` +./run-in-browser.js src/examples/web-compatible-example.ts +``` + +After running the above, navigate to http://localhost:8000 and open your browser's developer console to see the example executing. diff --git a/src/examples/api_exploration.ts b/src/examples/api-exploration.ts similarity index 96% rename from src/examples/api_exploration.ts rename to src/examples/api-exploration.ts index 427e37065d..6ed949a862 100644 --- a/src/examples/api_exploration.ts +++ b/src/examples/api-exploration.ts @@ -9,9 +9,9 @@ import { Int64, Provable, Struct, -} from 'snarkyjs'; +} from 'o1js'; -/* This file demonstrates the classes and functions available in snarkyjs */ +/* This file demonstrates the classes and functions available in o1js */ /* # Field */ @@ -86,7 +86,7 @@ const b3: Bool = b0.and(b1.not()).or(b1); ): T ``` - `Provable.if(b, x, y)` evaluates to `x` if `b` is true, and evalutes to `y` if `b` is false, + `Provable.if(b, x, y)` evaluates to `x` if `b` is true, and evaluates to `y` if `b` is false, so it works like a ternary if expression `b ? x : y`. The generic type T can be instantiated to primitive types like Bool, Field, or Group, or @@ -124,7 +124,7 @@ x.assertEquals(Int64.from(2)); /* # Signature */ -/* The standard library of snarkyJS comes with a Signature scheme. +/* The standard library of o1js comes with a Signature scheme. The message to be signed is an array of field elements, so any application level message data needs to be encoded as an array of field elements before being signed. */ @@ -149,8 +149,8 @@ console.assert(!signature.verify(pubKey, msg1).toBoolean()); */ /* You can initialize elements as literals as follows: */ -let g0 = new Group(-1, 2); -let g1 = new Group({ x: -2, y: 2 }); +let g0 = Group.from(-1, 2); +let g1 = new Group({ x: -1, y: 2 }); /* There is also a predefined generator. */ let g2 = Group.generator; diff --git a/src/examples/benchmarks/foreign-field.ts b/src/examples/benchmarks/foreign-field.ts new file mode 100644 index 0000000000..30190324ef --- /dev/null +++ b/src/examples/benchmarks/foreign-field.ts @@ -0,0 +1,31 @@ +import { Crypto, Provable, createForeignField } from 'o1js'; + +class ForeignScalar extends createForeignField( + Crypto.CurveParams.Secp256k1.modulus +) {} + +function main() { + let s = Provable.witness( + ForeignScalar.Canonical.provable, + ForeignScalar.random + ); + let t = Provable.witness( + ForeignScalar.Canonical.provable, + ForeignScalar.random + ); + s.mul(t); +} + +console.time('running constant version'); +main(); +console.timeEnd('running constant version'); + +console.time('running witness generation & checks'); +await Provable.runAndCheck(main); +console.timeEnd('running witness generation & checks'); + +console.time('creating constraint system'); +let cs = await Provable.constraintSystem(main); +console.timeEnd('creating constraint system'); + +console.log(cs.summary()); diff --git a/src/examples/benchmarks/hash-witness.ts b/src/examples/benchmarks/hash-witness.ts new file mode 100644 index 0000000000..69f5107a33 --- /dev/null +++ b/src/examples/benchmarks/hash-witness.ts @@ -0,0 +1,23 @@ +/** + * benchmark witness generation for an all-mul circuit + */ +import { Field, Provable, Poseidon } from 'o1js'; +import { tic, toc } from '../utils/tic-toc.js'; + +// parameters +let nPermutations = 1 << 12; // 2^12 x 11 rows < 2^16 rows, should just fit in a circuit + +// the circuit: hash a number n times +let xConst = Field.random(); + +function main(nMuls: number) { + let x = Provable.witness(Field, () => xConst); + let z = x; + for (let i = 0; i < nMuls; i++) { + z = Poseidon.hash([z, x]); + } +} + +tic('run and check'); +await Provable.runAndCheck(() => main(nPermutations)); +toc(); diff --git a/src/examples/benchmarks/import.ts b/src/examples/benchmarks/import.ts new file mode 100644 index 0000000000..fdd4340209 --- /dev/null +++ b/src/examples/benchmarks/import.ts @@ -0,0 +1,5 @@ +let start = performance.now(); +await import('../../snarky.js'); +let time = performance.now() - start; + +console.log(`import jsoo: ${time.toFixed(0)}ms`); diff --git a/src/examples/benchmarks/import.web.ts b/src/examples/benchmarks/import.web.ts new file mode 100644 index 0000000000..6c8c06e62b --- /dev/null +++ b/src/examples/benchmarks/import.web.ts @@ -0,0 +1,5 @@ +let start = performance.now(); +await import('o1js'); +let time = performance.now() - start; + +console.log(`import jsoo: ${time.toFixed(0)}ms`); diff --git a/src/examples/benchmarks/keccak-witness.ts b/src/examples/benchmarks/keccak-witness.ts new file mode 100644 index 0000000000..e4509c5019 --- /dev/null +++ b/src/examples/benchmarks/keccak-witness.ts @@ -0,0 +1,10 @@ +import { Hash, Bytes, Provable } from 'o1js'; + +let Bytes32 = Bytes(32); + +console.time('keccak witness'); +await Provable.runAndCheck(() => { + let bytes = Provable.witness(Bytes32.provable, () => Bytes32.random()); + Hash.Keccak256.hash(bytes); +}); +console.timeEnd('keccak witness'); diff --git a/src/examples/benchmarks/mul-web.ts b/src/examples/benchmarks/mul-web.ts index 17a9abbb24..1ad1d31912 100644 --- a/src/examples/benchmarks/mul-web.ts +++ b/src/examples/benchmarks/mul-web.ts @@ -1,8 +1,8 @@ /** * benchmark a circuit filled with generic gates */ -import { Circuit, Field, Provable, circuitMain, Experimental } from 'snarkyjs'; -let { ZkProgram } = Experimental; +import { Circuit, Field, Provable, circuitMain, ZkProgram } from 'o1js'; +import { tic, toc } from '../utils/tic-toc.js'; // parameters let nMuls = (1 << 16) + (1 << 15); // not quite 2^17 generic gates = not quite 2^16 rows @@ -20,8 +20,8 @@ function main(nMuls: number) { } } -function getRows(nMuls: number) { - let { rows } = Provable.constraintSystem(() => main(nMuls)); +async function getRows(nMuls: number) { + let { rows } = await Provable.constraintSystem(() => main(nMuls)); return rows; } @@ -38,10 +38,11 @@ function simpleKimchiCircuit(nMuls: number) { function picklesCircuit(nMuls: number) { return ZkProgram({ + name: 'mul-chain', methods: { run: { privateInputs: [], - method() { + async method() { main(nMuls); }, }, @@ -49,23 +50,9 @@ function picklesCircuit(nMuls: number) { }); } -// timing helpers -let timingStack: [string, number][] = []; -let i = 0; -function tic(label = `Run command ${i++}`) { - console.log(`${label}... `); - timingStack.push([label, Date.now()]); -} -function toc() { - let [label, start] = timingStack.pop()!; - let time = (Date.now() - start) / 1000; - console.log(`\r${label}... ${time.toFixed(3)} sec\n`); - return time; -} - // the script -console.log('circuit size (without pickles overhead)', getRows(nMuls)); +console.log('circuit size (without pickles overhead)', await getRows(nMuls)); if (withPickles) { let circuit = picklesCircuit(nMuls); diff --git a/src/examples/benchmarks/mul-witness.ts b/src/examples/benchmarks/mul-witness.ts new file mode 100644 index 0000000000..947360cbd3 --- /dev/null +++ b/src/examples/benchmarks/mul-witness.ts @@ -0,0 +1,23 @@ +/** + * benchmark witness generation for an all-mul circuit + */ +import { Field, Provable } from 'o1js'; +import { tic, toc } from '../utils/tic-toc.js'; + +// parameters +let nMuls = (1 << 16) + (1 << 15); // not quite 2^17 generic gates = not quite 2^16 rows + +// the circuit: multiply a number with itself n times +let xConst = Field.random(); + +function main(nMuls: number) { + let x = Provable.witness(Field, () => xConst); + let z = x; + for (let i = 0; i < nMuls; i++) { + z = z.mul(x); + } +} + +tic('run and check'); +await Provable.runAndCheck(() => main(nMuls)); +toc(); diff --git a/src/examples/benchmarks/mul.ts b/src/examples/benchmarks/mul.ts index b11ac1b511..97211864d7 100644 --- a/src/examples/benchmarks/mul.ts +++ b/src/examples/benchmarks/mul.ts @@ -1,9 +1,8 @@ /** * benchmark a circuit filled with generic gates */ -import { Circuit, Field, Provable, circuitMain, Experimental } from 'snarkyjs'; -import { tic, toc } from '../zkapps/tictoc.js'; -let { ZkProgram } = Experimental; +import { Circuit, Field, Provable, circuitMain, ZkProgram } from 'o1js'; +import { tic, toc } from '../utils/tic-toc.node.js'; // parameters let nMuls = (1 << 16) + (1 << 15); // not quite 2^17 generic gates = not quite 2^16 rows @@ -20,8 +19,8 @@ function main(nMuls: number) { } } -function getRows(nMuls: number) { - let { rows } = Provable.constraintSystem(() => main(nMuls)); +async function getRows(nMuls: number) { + let { rows } = await Provable.constraintSystem(() => main(nMuls)); return rows; } @@ -37,10 +36,11 @@ function simpleKimchiCircuit(nMuls: number) { function picklesCircuit(nMuls: number) { return ZkProgram({ + name: 'mul-chain', methods: { run: { privateInputs: [], - method() { + async method() { main(nMuls); }, }, @@ -48,7 +48,7 @@ function picklesCircuit(nMuls: number) { }); } -console.log('circuit size (without pickles overhead)', getRows(nMuls)); +console.log('circuit size (without pickles overhead)', await getRows(nMuls)); if (withPickles) { let circuit = picklesCircuit(nMuls); diff --git a/src/examples/circuit_string.ts b/src/examples/circuit-string.ts similarity index 85% rename from src/examples/circuit_string.ts rename to src/examples/circuit-string.ts index a121da2330..c48ac339d0 100644 --- a/src/examples/circuit_string.ts +++ b/src/examples/circuit-string.ts @@ -1,16 +1,9 @@ -import { - isReady, - CircuitString, - SmartContract, - method, - Mina, - PrivateKey, -} from 'snarkyjs'; +import { CircuitString, SmartContract, method, Mina, PrivateKey } from 'o1js'; import * as assert from 'assert/strict'; // circuit which tests a couple of string features class MyContract extends SmartContract { - @method checkString(s: CircuitString) { + @method async checkString(s: CircuitString) { let sWithExclamation = s.append(CircuitString.fromString('!')); sWithExclamation .equals(CircuitString.fromString('a string!')) @@ -19,28 +12,27 @@ class MyContract extends SmartContract { } } -await isReady; let address = PrivateKey.random().toPublicKey(); console.log('compile...'); await MyContract.compile(); // should work console.log('prove...'); -let tx = await Mina.transaction(() => { - new MyContract(address).checkString(CircuitString.fromString('a string')); -}); +let tx = await Mina.transaction(() => + new MyContract(address).checkString(CircuitString.fromString('a string')) +); await tx.prove(); console.log('test 1 - ok'); // should work -tx = await Mina.transaction(() => { - new MyContract(address).checkString(CircuitString.fromString('some string')); -}); +tx = await Mina.transaction(() => + new MyContract(address).checkString(CircuitString.fromString('some string')) +); await tx.prove(); console.log('test 2 - ok'); // should fail -let fails = await Mina.transaction(() => { - new MyContract(address).checkString(CircuitString.fromString('different')); -}) +let fails = await Mina.transaction(() => + new MyContract(address).checkString(CircuitString.fromString('different')) +) .then(() => false) .catch(() => true); if (!fails) Error('proof was supposed to fail'); diff --git a/src/examples/circuit/README.md b/src/examples/circuit/README.md new file mode 100644 index 0000000000..1eecf13b11 --- /dev/null +++ b/src/examples/circuit/README.md @@ -0,0 +1,7 @@ +# `Circuit` examples + +These examples show how to use `Circuit`, which is a simple API to write a single circuit and create proofs for it. + +In contrast to `ZkProgram`, `Circuit` does not pass through Pickles, but creates a proof with Kimchi directly. Therefore, it does not support recursion, but is also much faster. + +Note that `Circuit` proofs are not compatible with Mina zkApps. diff --git a/src/examples/circuit/ecdsa.ts b/src/examples/circuit/ecdsa.ts new file mode 100644 index 0000000000..8dfe02627e --- /dev/null +++ b/src/examples/circuit/ecdsa.ts @@ -0,0 +1,45 @@ +import { + Circuit, + circuitMain, + public_, + Crypto, + createEcdsa, + createForeignCurve, + Bytes, + assert, +} from 'o1js'; + +export { Secp256k1, Ecdsa, Bytes32, Reserves }; + +class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} +class Ecdsa extends createEcdsa(Secp256k1) {} +class Bytes32 extends Bytes(32) {} + +class Reserves extends Circuit { + @circuitMain + static main( + @public_ message: Bytes32, + signature: Ecdsa, + publicKey: Secp256k1 + ) { + assert(signature.verify(message, publicKey)); + } +} + +console.time('generateKeypair'); +let kp = await Reserves.generateKeypair(); +console.timeEnd('generateKeypair'); + +let message = Bytes32.random(); +let privateKey = Secp256k1.Scalar.random(); +let publicKey = Secp256k1.generator.scale(privateKey); +let signature = Ecdsa.sign(message.toBytes(), privateKey.toBigInt()); + +console.time('prove'); +let proof = await Reserves.prove([signature, publicKey], [message], kp); +console.timeEnd('prove'); + +console.time('verify'); +let isValid = await Reserves.verify([message], kp.verificationKey(), proof); +assert(isValid, 'verifies'); +console.timeEnd('verify'); diff --git a/src/examples/ex00_preimage.ts b/src/examples/circuit/preimage.ts similarity index 71% rename from src/examples/ex00_preimage.ts rename to src/examples/circuit/preimage.ts index 9801c358c9..22b3099225 100644 --- a/src/examples/ex00_preimage.ts +++ b/src/examples/circuit/preimage.ts @@ -1,19 +1,11 @@ -import { - Poseidon, - Field, - Circuit, - circuitMain, - public_, - isReady, -} from 'snarkyjs'; - -/* Exercise 0: - -Public input: a hash value h -Prove: - I know a value x such that hash(x) = h -*/ - +import { Poseidon, Field, Circuit, circuitMain, public_ } from 'o1js'; + +/** + * Public input: a hash value h + * + * Prove: + * I know a value x such that hash(x) = h + */ class Main extends Circuit { @circuitMain static main(preimage: Field, @public_ hash: Field) { @@ -21,8 +13,6 @@ class Main extends Circuit { } } -await isReady; - console.log('generating keypair...'); const kp = await Main.generateKeypair(); diff --git a/src/examples/ex02_root.ts b/src/examples/circuit/root.ts similarity index 66% rename from src/examples/ex02_root.ts rename to src/examples/circuit/root.ts index 048da1ecbf..15ab297ee4 100644 --- a/src/examples/ex02_root.ts +++ b/src/examples/circuit/root.ts @@ -1,17 +1,15 @@ -import { Field, Circuit, circuitMain, public_, isReady } from 'snarkyjs'; - -await isReady; - -/* Exercise 2: - -Public input: a field element x -Prove: - I know a value y that is a cube root of x. -*/ - +import { Field, Circuit, circuitMain, public_, Gadgets } from 'o1js'; + +/** + * Public input: a field element x + * + * Prove: + * I know a value y < 2^64 that is a cube root of x. + */ class Main extends Circuit { @circuitMain - static main(y: Field, @public_ x: Field) { + static main(@public_ x: Field, y: Field) { + Gadgets.rangeCheck64(y); let y3 = y.square().mul(y); y3.assertEquals(x); } @@ -24,8 +22,8 @@ console.timeEnd('generating keypair...'); console.log('prove...'); console.time('prove...'); -const x = new Field(8); -const y = new Field(2); +const x = Field(8); +const y = Field(2); const proof = await Main.prove([y], [x], kp); console.timeEnd('prove...'); diff --git a/src/examples/commonjs.cjs b/src/examples/commonjs.cjs index d30453c024..4974390e33 100644 --- a/src/examples/commonjs.cjs +++ b/src/examples/commonjs.cjs @@ -1,5 +1,5 @@ /** - * Tests that snarkyjs can be imported and used from commonJS files + * Tests that o1js can be imported and used from commonJS files */ let { Field, @@ -10,7 +10,7 @@ let { AccountUpdate, declareState, declareMethods, -} = require('snarkyjs'); +} = require('o1js'); class SimpleZkapp extends SmartContract { constructor(address) { @@ -25,12 +25,11 @@ class SimpleZkapp extends SmartContract { this.x.set(initialState); } - update(y) { + async update(y) { this.emitEvent('update', y); this.emitEvent('update', y); - this.account.balance.assertEquals(this.account.balance.get()); - let x = this.x.get(); - this.x.assertEquals(x); + this.account.balance.requireEquals(this.account.balance.get()); + let x = this.x.getAndRequireEquals(); this.x.set(x.add(y)); } } @@ -56,9 +55,9 @@ async function main() { await SimpleZkapp.compile(); console.log('deploy'); - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); + await zkapp.deploy(); }); await tx.sign([feePayerKey, zkappKey]).send(); diff --git a/src/examples/constraint-system.ts b/src/examples/constraint-system.ts new file mode 100644 index 0000000000..e6755f3ba4 --- /dev/null +++ b/src/examples/constraint-system.ts @@ -0,0 +1,16 @@ +import { Field, Poseidon, Provable } from 'o1js'; + +let hash = Poseidon.hash([Field(1), Field(-1)]); + +let { rows, digest, publicInputSize, print } = await Provable.constraintSystem( + () => { + let x = Provable.witness(Field, () => Field(1)); + let y = Provable.witness(Field, () => Field(-1)); + x.add(y).assertEquals(Field(0)); + let z = Poseidon.hash([x, y]); + z.assertEquals(hash); + } +); + +print(); +console.log({ rows, digest, publicInputSize }); diff --git a/src/examples/constraint_system.ts b/src/examples/constraint_system.ts deleted file mode 100644 index eca940c1f0..0000000000 --- a/src/examples/constraint_system.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Field, Poseidon, Provable } from 'snarkyjs'; - -let hash = Poseidon.hash([Field(1), Field(-1)]); - -let { rows, digest, gates, publicInputSize } = Provable.constraintSystem(() => { - let x = Provable.witness(Field, () => Field(1)); - let y = Provable.witness(Field, () => Field(-1)); - x.add(y).assertEquals(Field(0)); - let z = Poseidon.hash([x, y]); - z.assertEquals(hash); -}); - -console.log(JSON.stringify(gates)); -console.log({ rows, digest, publicInputSize }); diff --git a/src/examples/crypto/README.md b/src/examples/crypto/README.md new file mode 100644 index 0000000000..c2f913defa --- /dev/null +++ b/src/examples/crypto/README.md @@ -0,0 +1,6 @@ +# Crypto examples + +These examples show how to use some of the crypto primitives that are supported in provable o1js code. + +- Non-native field arithmetic: `foreign-field.ts` +- Non-native ECDSA verification: `ecdsa.ts` diff --git a/src/examples/crypto/ecdsa/ecdsa.ts b/src/examples/crypto/ecdsa/ecdsa.ts new file mode 100644 index 0000000000..8e9e951ee6 --- /dev/null +++ b/src/examples/crypto/ecdsa/ecdsa.ts @@ -0,0 +1,45 @@ +import { + ZkProgram, + Crypto, + createEcdsa, + createForeignCurve, + Bool, + Bytes, +} from 'o1js'; + +export { keccakAndEcdsa, ecdsa, Secp256k1, Ecdsa, Bytes32 }; + +class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} +class Scalar extends Secp256k1.Scalar {} +class Ecdsa extends createEcdsa(Secp256k1) {} +class Bytes32 extends Bytes(32) {} + +const keccakAndEcdsa = ZkProgram({ + name: 'ecdsa', + publicInput: Bytes32.provable, + publicOutput: Bool, + + methods: { + verifyEcdsa: { + privateInputs: [Ecdsa.provable, Secp256k1.provable], + async method(message: Bytes32, signature: Ecdsa, publicKey: Secp256k1) { + return signature.verify(message, publicKey); + }, + }, + }, +}); + +const ecdsa = ZkProgram({ + name: 'ecdsa-only', + publicInput: Scalar.provable, + publicOutput: Bool, + + methods: { + verifySignedHash: { + privateInputs: [Ecdsa.provable, Secp256k1.provable], + async method(message: Scalar, signature: Ecdsa, publicKey: Secp256k1) { + return signature.verifySignedHash(message, publicKey); + }, + }, + }, +}); diff --git a/src/examples/crypto/ecdsa/run.ts b/src/examples/crypto/ecdsa/run.ts new file mode 100644 index 0000000000..c342a6433d --- /dev/null +++ b/src/examples/crypto/ecdsa/run.ts @@ -0,0 +1,36 @@ +import { Secp256k1, Ecdsa, keccakAndEcdsa, ecdsa, Bytes32 } from './ecdsa.js'; +import assert from 'assert'; + +// create an example ecdsa signature + +let privateKey = Secp256k1.Scalar.random(); +let publicKey = Secp256k1.generator.scale(privateKey); + +let message = Bytes32.fromString("what's up"); + +let signature = Ecdsa.sign(message.toBytes(), privateKey.toBigInt()); + +// investigate the constraint system generated by ECDSA verify + +console.time('ecdsa verify only (build constraint system)'); +let csEcdsa = await ecdsa.analyzeMethods(); +console.timeEnd('ecdsa verify only (build constraint system)'); +console.log(csEcdsa.verifySignedHash.summary()); + +console.time('keccak + ecdsa verify (build constraint system)'); +let cs = await keccakAndEcdsa.analyzeMethods(); +console.timeEnd('keccak + ecdsa verify (build constraint system)'); +console.log(cs.verifyEcdsa.summary()); + +// compile and prove + +console.time('keccak + ecdsa verify (compile)'); +await keccakAndEcdsa.compile(); +console.timeEnd('keccak + ecdsa verify (compile)'); + +console.time('keccak + ecdsa verify (prove)'); +let proof = await keccakAndEcdsa.verifyEcdsa(message, signature, publicKey); +console.timeEnd('keccak + ecdsa verify (prove)'); + +proof.publicOutput.assertTrue('signature verifies'); +assert(await keccakAndEcdsa.verify(proof), 'proof verifies'); diff --git a/src/examples/crypto/foreign-field.ts b/src/examples/crypto/foreign-field.ts new file mode 100644 index 0000000000..044d8ebda6 --- /dev/null +++ b/src/examples/crypto/foreign-field.ts @@ -0,0 +1,116 @@ +/** + * This example explores the ForeignField API! + * + * We shed light on the subtleties of different variants of foreign field: + * Unreduced, AlmostReduced, and Canonical. + */ +import assert from 'assert'; +import { + createForeignField, + AlmostForeignField, + CanonicalForeignField, + Scalar, + SmartContract, + method, + Provable, + state, + State, +} from 'o1js'; + +// Let's create a small finite field: F_17 + +class SmallField extends createForeignField(17n) {} + +let x = SmallField.from(16); +x.assertEquals(-1); // 16 = -1 (mod 17) +x.mul(x).assertEquals(1); // 16 * 16 = 15 * 17 + 1 = 1 (mod 17) + +// most arithmetic operations return "unreduced" fields, i.e., fields that could be larger than the modulus: + +let z = x.add(x); +assert(z instanceof SmallField.Unreduced); + +// note: "unreduced" doesn't usually mean that the underlying witness is larger than the modulus. +// it just means we haven't _proved_ so.. which means a malicious prover _could_ have managed to make it larger. + +// unreduced fields can be added and subtracted, but not be used in multiplcation: + +z.add(1).sub(x).assertEquals(0); // works + +assert((z as any).mul === undefined); // z.mul() is not defined +assert((z as any).inv === undefined); +assert((z as any).div === undefined); + +// to do multiplication, you need "almost reduced" fields: + +let y: AlmostForeignField = z.assertAlmostReduced(); // adds constraints to prove that z is, in fact, reduced +assert(y instanceof SmallField.AlmostReduced); + +y.mul(y).assertEquals(4); // y.mul() is defined +assert(y.mul(y) instanceof SmallField.Unreduced); // but y.mul() returns an unreduced field again + +y.inv().mul(y).assertEquals(1); // y.inv() is defined (and returns an AlmostReduced field!) + +// to do many multiplications, it's more efficient to reduce fields in batches of 3 elements: +// (in fact, asserting that 3 elements are reduced is almost as cheap as asserting that 1 element is reduced) + +let z1 = y.mul(7); +let z2 = y.add(11); +let z3 = y.sub(13); + +let [z1r, z2r, z3r] = SmallField.assertAlmostReduced(z1, z2, z3); + +z1r.mul(z2r); +z2r.div(z3r); + +// here we get to the reason _why_ we have different variants of foreign fields: +// always proving that they are reduced after every operation would be super inefficient! + +// fields created from constants are already reduced -- in fact, they are _fully reduced_ or "canonical": + +let constant: CanonicalForeignField = SmallField.from(1); +assert(constant instanceof SmallField.Canonical); + +SmallField.from(10000n) satisfies CanonicalForeignField; // works because `from()` takes the input mod p +SmallField.from(-1) satisfies CanonicalForeignField; // works because `from()` takes the input mod p + +// canonical fields are a special case of almost reduced fields at the type level: +constant satisfies AlmostForeignField; +constant.mul(constant); + +// the cheapest way to prove that an existing field element is canonical is to show that it is equal to a constant: + +let u = z.add(x); +let uCanonical = u.assertEquals(-3); +assert(uCanonical instanceof SmallField.Canonical); + +// to use the different variants of foreign fields as smart contract inputs, you might want to create a class for them: +class AlmostSmallField extends SmallField.AlmostReduced {} + +class MyContract extends SmartContract { + @state(AlmostSmallField.provable) x = State(); + + @method async myMethod(y: AlmostSmallField) { + let x = y.mul(2); + Provable.log(x); + this.x.set(x.assertAlmostReduced()); + } +} +await MyContract.analyzeMethods(); // works + +// btw - we support any finite field up to 259 bits. for example, the seqp256k1 base field: +let Fseqp256k1 = createForeignField((1n << 256n) - (1n << 32n) - 0b1111010001n); + +// or the Pallas scalar field, to do arithmetic on scalars: +let Fq = createForeignField(Scalar.ORDER); + +// also, you can use a number that's not a prime. +// for example, you might want to create a UInt256 type: +let UInt256 = createForeignField(1n << 256n); + +// and now you can do arithmetic modulo 2^256! +let a = UInt256.from(1n << 255n); +let b = UInt256.from((1n << 255n) + 7n); +a.add(b).assertEquals(7); + +// have fun proving finite field algorithms! diff --git a/src/examples/crypto/sha256/run.ts b/src/examples/crypto/sha256/run.ts new file mode 100644 index 0000000000..f506f7fe5d --- /dev/null +++ b/src/examples/crypto/sha256/run.ts @@ -0,0 +1,23 @@ +import { Bytes12, SHA256Program } from './sha256.js'; + +console.time('compile'); +await SHA256Program.compile(); +console.timeEnd('compile'); + +let preimage = Bytes12.fromString('hello world!'); + +console.log('sha256 rows:', (await SHA256Program.analyzeMethods()).sha256.rows); + +console.time('prove'); +let proof = await SHA256Program.sha256(preimage); +console.timeEnd('prove'); +let isValid = await SHA256Program.verify(proof); + +console.log('digest:', proof.publicOutput.toHex()); + +if ( + proof.publicOutput.toHex() !== + '7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9' +) + throw new Error('Invalid sha256 digest!'); +if (!isValid) throw new Error('Invalid proof'); diff --git a/src/examples/crypto/sha256/sha256.ts b/src/examples/crypto/sha256/sha256.ts new file mode 100644 index 0000000000..a6e335fd7f --- /dev/null +++ b/src/examples/crypto/sha256/sha256.ts @@ -0,0 +1,18 @@ +import { Bytes, Gadgets, ZkProgram } from 'o1js'; + +export { SHA256Program, Bytes12 }; + +class Bytes12 extends Bytes(12) {} + +let SHA256Program = ZkProgram({ + name: 'sha256', + publicOutput: Bytes(32).provable, + methods: { + sha256: { + privateInputs: [Bytes12.provable], + async method(xs: Bytes12) { + return Gadgets.SHA256.hash(xs); + }, + }, + }, +}); diff --git a/src/examples/deploy/.gitignore b/src/examples/deploy/.gitignore deleted file mode 100644 index 378eac25d3..0000000000 --- a/src/examples/deploy/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build diff --git a/src/examples/deploy/compile.ts b/src/examples/deploy/compile.ts deleted file mode 100644 index fba39fc770..0000000000 --- a/src/examples/deploy/compile.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { isReady, PrivateKey, shutdown } from 'snarkyjs'; -import SimpleZkapp from './simple_zkapp.js'; -import { writeFileSync, mkdirSync, existsSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; - -await isReady; - -let zkappKey = PrivateKey.random(); -let zkappAddress = zkappKey.toPublicKey(); - -let { verificationKey } = await SimpleZkapp.compile(); -storeArtifact(SimpleZkapp, { verificationKey }); - -shutdown(); - -function storeArtifact(SmartContract: Function, json: unknown) { - let thisFolder = dirname(fileURLToPath(import.meta.url)); - if (!existsSync(`${thisFolder}/build`)) { - mkdirSync(`${thisFolder}/build`); - } - writeFileSync( - `${thisFolder}/build/${SmartContract.name}.json`, - JSON.stringify(json) - ); -} diff --git a/src/examples/deploy/deploy.ts b/src/examples/deploy/deploy.ts deleted file mode 100644 index 041c8d4d7e..0000000000 --- a/src/examples/deploy/deploy.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { isReady, PrivateKey, shutdown, Mina } from 'snarkyjs'; -import SimpleZkapp from './simple_zkapp.js'; -import { readFileSync, existsSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; - -await isReady; - -// TODO: get keys from somewhere else; for now we assume the account is already funded -let zkappKey = PrivateKey.random(); -let zkappAddress = zkappKey.toPublicKey(); - -// read verification key from disk -let artifact = readArtifact(SimpleZkapp); -if (artifact === undefined) - throw Error('No verification key found! Use compile.ts first'); -let { verificationKey } = artifact; - -// produce and log the transaction json; the fee payer is a dummy which has to be added later, by the signing logic -let tx = await Mina.transaction(() => { - new SimpleZkapp(zkappAddress).deploy({ verificationKey }); -}); -let transactionJson = tx.sign([zkappKey]).toJSON(); - -console.log(transactionJson); - -shutdown(); - -function readArtifact(SmartContract: Function) { - let thisFolder = dirname(fileURLToPath(import.meta.url)); - let jsonFile = `${thisFolder}/build/${SmartContract.name}.json`; - if (!existsSync(`${thisFolder}/build`) || !existsSync(jsonFile)) { - return undefined; - } - return JSON.parse(readFileSync(jsonFile, 'utf-8')); -} diff --git a/src/examples/deploy/simple_zkapp.ts b/src/examples/deploy/simple_zkapp.ts deleted file mode 100644 index 171dc06c39..0000000000 --- a/src/examples/deploy/simple_zkapp.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Field, state, State, method, SmartContract } from 'snarkyjs'; - -export { SimpleZkapp as default }; - -const initialState = 10; - -class SimpleZkapp extends SmartContract { - @state(Field) x = State(); - - init() { - super.init(); - this.x.set(Field(initialState)); - } - - @method update(y: Field) { - let x = this.x.get(); - this.x.set(x.add(y)); - } -} diff --git a/src/examples/encoding-bijective.ts b/src/examples/encoding-bijective.ts index b0e176d719..789baa8740 100644 --- a/src/examples/encoding-bijective.ts +++ b/src/examples/encoding-bijective.ts @@ -1,6 +1,5 @@ -import { Field, isReady, shutdown, Encoding } from 'snarkyjs'; +import { Field, Encoding } from 'o1js'; -await isReady; let n = 1000; let { toBytes, fromBytes } = Encoding.Bijective.Fp; @@ -21,8 +20,6 @@ let bytesEqual = arrayEqual([...bytes], [...newBytes]); if (!bytesEqual) throw Error('roundtrip bytes -> fields -> bytes failed'); else console.log('bytes -> fields -> bytes: ok'); -shutdown(); - function arrayEqual(a: T[], b: T[], isEqual?: (a: T, b: T) => boolean) { if (isEqual === undefined) isEqual = (a, b) => a === b; if (a.length !== b.length) return false; diff --git a/src/examples/encryption.ts b/src/examples/encryption.ts index 3798e45331..74107e49d9 100644 --- a/src/examples/encryption.ts +++ b/src/examples/encryption.ts @@ -1,13 +1,4 @@ -import { - Encryption, - Encoding, - PrivateKey, - isReady, - Circuit, - Provable, -} from 'snarkyjs'; - -await isReady; +import { Encryption, Encoding, PrivateKey, Provable } from 'o1js'; // generate keys let privateKey = PrivateKey.random(); @@ -30,7 +21,7 @@ console.log(`Recovered message: "${decryptedMessage}"`); // the same but in a checked computation -Provable.runAndCheck(() => { +await Provable.runAndCheck(() => { // encrypt let cipherText = Encryption.encrypt(messageFields, publicKey); @@ -76,7 +67,7 @@ console.log(`Recovered message: "${decryptedMessage}"`); // the same but in a checked computation -Provable.runAndCheck(() => { +await Provable.runAndCheck(() => { // encrypt let cipherText = Encryption.encrypt(messageFields, publicKey); diff --git a/src/examples/ex01_small_preimage.ts b/src/examples/ex01_small_preimage.ts deleted file mode 100644 index f9ca7846ca..0000000000 --- a/src/examples/ex01_small_preimage.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - Poseidon, - Field, - Circuit, - circuitMain, - public_, - isReady, -} from 'snarkyjs'; - -await isReady; - -/* Exercise 1: - -Public input: a hash value h -Prove: - I know a value x < 2^32 such that hash(x) = h -*/ - -class Main extends Circuit { - @circuitMain - static main(preimage: Field, @public_ hash: Field) { - preimage.toBits(32); - Poseidon.hash([preimage]).assertEquals(hash); - } -} - -const kp = await Main.generateKeypair(); - -const preimage = Field.fromBits(Field.random().toBits().slice(0, 32)); -const hash = Poseidon.hash([preimage]); -const pi = await Main.prove([preimage], [hash], kp); -console.log('proof', pi); diff --git a/src/examples/fetch-live.ts b/src/examples/fetch-live.ts new file mode 100644 index 0000000000..b432580352 --- /dev/null +++ b/src/examples/fetch-live.ts @@ -0,0 +1,92 @@ +import { expect } from 'expect'; +import { Lightnet, Mina, PrivateKey, UInt64, fetchAccount } from 'o1js'; + +const useCustomLocalNetwork = process.env.USE_CUSTOM_LOCAL_NETWORK === 'true'; +Mina.setActiveInstance(configureMinaNetwork()); +const transactionFee = 100_000_000; +let defaultNetworkConstants: Mina.NetworkConstants = { + genesisTimestamp: UInt64.from(0), + slotTime: UInt64.from(3 * 60 * 1000), + accountCreationFee: UInt64.from(1_000_000_000), +}; +const { sender } = await configureFeePayer(); + +await checkDefaultNetworkConstantsFetching(); +await checkActualNetworkConstantsFetching(); + +await tearDown(); + +async function checkDefaultNetworkConstantsFetching() { + console.log( + '\nCase #1: check that default network constants can be fetched outside of the transaction scope.' + ); + const networkConstants = Mina.getNetworkConstants(); + expect(defaultNetworkConstants).toEqual(networkConstants); + logNetworkConstants(networkConstants); +} + +async function checkActualNetworkConstantsFetching() { + console.log( + '\nCase #2: check that actual network constants can be fetched within the transaction scope.' + ); + let networkConstants: Mina.NetworkConstants | undefined; + await Mina.transaction({ sender, fee: transactionFee }, async () => { + networkConstants = Mina.getNetworkConstants(); + }); + expect(networkConstants?.slotTime).not.toBeUndefined(); + expect(networkConstants?.genesisTimestamp).not.toBeUndefined(); + expect(defaultNetworkConstants).not.toEqual(networkConstants); + logNetworkConstants(networkConstants); +} + +function configureMinaNetwork() { + const minaGraphQlEndpoint = useCustomLocalNetwork + ? 'http://localhost:8080/graphql' + : 'https://berkeley.minascan.io/graphql'; + return Mina.Network({ + mina: minaGraphQlEndpoint, + archive: useCustomLocalNetwork + ? 'http://localhost:8282' + : 'https://api.minascan.io/archive/berkeley/v1/graphql', + lightnetAccountManager: 'http://localhost:8181', + }); +} + +async function configureFeePayer() { + const senderKey = useCustomLocalNetwork + ? (await Lightnet.acquireKeyPair()).privateKey + : PrivateKey.random(); + const sender = senderKey.toPublicKey(); + if (!useCustomLocalNetwork) { + console.log(`\nFunding the fee payer account.`); + await Mina.faucet(sender); + } + console.log(`\nFetching the fee payer account information.`); + const accountDetails = (await fetchAccount({ publicKey: sender })).account; + console.log( + `Using the fee payer account ${sender.toBase58()} with nonce: ${ + accountDetails?.nonce + } and balance: ${accountDetails?.balance}.` + ); + return { sender, senderKey }; +} + +async function tearDown() { + const keyPairReleaseMessage = await Lightnet.releaseKeyPair({ + publicKey: sender.toBase58(), + }); + if (keyPairReleaseMessage) console.info('\n' + keyPairReleaseMessage); +} + +function logNetworkConstants( + networkConstants: Mina.NetworkConstants | undefined +) { + console.log(`Account creation fee: ${networkConstants?.accountCreationFee}`); + console.log(`Slot time: ${networkConstants?.slotTime}`); + console.log(`Genesis timestamp: ${networkConstants?.genesisTimestamp}`); + console.log( + `Genesis date: ${new Date( + Number(networkConstants?.genesisTimestamp.toString() ?? '0') + )}` + ); +} diff --git a/src/examples/fetch.ts b/src/examples/fetch.ts index 76d876f592..87fece718d 100644 --- a/src/examples/fetch.ts +++ b/src/examples/fetch.ts @@ -1,14 +1,11 @@ import { fetchAccount, - isReady, setGraphqlEndpoints, - shutdown, fetchLastBlock, PublicKey, Types, -} from 'snarkyjs'; +} from 'o1js'; -await isReady; setGraphqlEndpoints([ 'https://proxy.berkeley.minaexplorer.com/graphql', 'https://berkeley.minascan.io/graphql', @@ -25,5 +22,3 @@ console.log('account', Types.Account.toJSON(account!)); let block = await fetchLastBlock(); console.log('last block', JSON.stringify(block, null, 2)); - -await shutdown(); diff --git a/src/examples/internals/README.md b/src/examples/internals/README.md new file mode 100644 index 0000000000..ddc6d96fe6 --- /dev/null +++ b/src/examples/internals/README.md @@ -0,0 +1,5 @@ +# Examples: Internals + +This folder contains examples which highlight inner workings and less-documented behaviours of o1js. + +These examples might be useful for advanced users and contributors. diff --git a/src/examples/internals/advanced-provable-types.ts b/src/examples/internals/advanced-provable-types.ts new file mode 100644 index 0000000000..e771206359 --- /dev/null +++ b/src/examples/internals/advanced-provable-types.ts @@ -0,0 +1,164 @@ +/** + * This example explains some inner workings of provable types at the hand of a particularly + * complex type: `AccountUpdate`. + */ +import assert from 'assert/strict'; +import { + AccountUpdate, + PrivateKey, + Provable, + Empty, + ProvableExtended, +} from 'o1js'; +import { expect } from 'expect'; + +/** + * Example of a complex provable type: `AccountUpdate` + */ +AccountUpdate satisfies Provable; +console.log(`an account update has ${AccountUpdate.sizeInFields()} fields`); + +let address = PrivateKey.random().toPublicKey(); +let accountUpdate = AccountUpdate.defaultAccountUpdate(address); +accountUpdate.body.callDepth = 5; +accountUpdate.lazyAuthorization = { kind: 'lazy-signature' }; + +/** + * Every provable type can be disassembled into its provable/in-circuit part (fields) + * and a non-provable part (auxiliary). + * + * The parts can be assembled back together to create a new object which is deeply equal to the old one. + */ +let fields = AccountUpdate.toFields(accountUpdate); +let aux = AccountUpdate.toAuxiliary(accountUpdate); +let accountUpdateRecovered = AccountUpdate.fromFields(fields, aux); +expect(accountUpdateRecovered.body).toEqual(accountUpdate.body); +expect(accountUpdateRecovered.lazyAuthorization).toEqual( + accountUpdate.lazyAuthorization +); + +/** + * Provable types which implement `ProvableExtended` can also be serialized to/from JSON. + * + * However, `AccountUpdate` specifically is a wrapper around an actual, core provable extended type. + * It has additional properties, like lazySignature, which are not part of the JSON representation + * and therefore aren't recovered. + */ +AccountUpdate satisfies ProvableExtended; +let json = AccountUpdate.toJSON(accountUpdate); +accountUpdateRecovered = AccountUpdate.fromJSON(json); +expect(accountUpdateRecovered.body).toEqual(accountUpdate.body); +expect(accountUpdateRecovered.lazyAuthorization).not.toEqual( + accountUpdate.lazyAuthorization +); + +/** + * Provable.runAndCheck() can be used to run a circuit in "prover mode". + * That means + * -) witness() and asProver() blocks are executed + * -) constraints are checked; failing assertions throw an error + */ +await Provable.runAndCheck(() => { + /** + * Provable.witness() is used to introduce all values to the circuit which are not hard-coded constants. + * + * Under the hood, it disassembles and reassembles the provable type with toFields(), toAuxiliary() and fromFields(). + */ + let accountUpdateWitness = Provable.witness( + AccountUpdate, + () => accountUpdate + ); + + /** + * The witness is "provably equal" to the original. + * (this, under hood, calls assertEqual on all fields returned by .toFields()). + */ + Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); + + /** + * Auxiliary parts are also recovered in the witness. + * Note, though, that this can't be enforced as part of a proof! + */ + assert( + accountUpdateWitness.body.callDepth === 5, + 'when witness block is executed, witness() recreates auxiliary parts of provable type' + ); + Provable.assertEqual( + PrivateKey, + (accountUpdateWitness.lazyAuthorization as any).privateKey, + (accountUpdate.lazyAuthorization as any).privateKey + ); +}); + +/** + * Provable.constraintSystem() runs the circuit in "compile mode". + * -) witness() and asProver() blocks are not executed + * -) fields don't have actual values attached to them; they're purely abstract variables + * -) constraints are not checked + */ +let result = await Provable.constraintSystem(() => { + /** + * In compile mode, witness() returns + * - abstract variables without values for fields + * - dummy data for auxiliary + */ + let accountUpdateWitness = Provable.witness( + AccountUpdate, + (): AccountUpdate => { + throw 'not executed anyway'; + } + ); + + /** + * Dummy data can take a different form depending on the provable type, + * but in most cases it's "all-zeroes" + */ + assert( + accountUpdateWitness.body.callDepth === 0, + 'when witness block is not executed, witness() returns dummy data' + ); + Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); +}); + +/** + * Provable.constraintSystem() is a great way to investigate how many constraints operations take. + * + * Note that even just witnessing stuff takes constraints, for provable types which define a check() method. + * Bools are proved to be 0 or 1, UInt64 is proved to be within [0, 2^64), etc. + */ +console.log( + `witnessing an account update and comparing it to another one creates ${result.rows} rows` +); + +/** + * For account updates specifically, we typically don't want all the subfield checks. That's because + * account updates are usually tied the _public input_. The public input is checked on the verifier side + * already, including the well-formedness of its parts, so there's no need to include that in the proof. + * + * This is why we have this custom way of witnessing account updates, with the `skipCheck` option. + */ +result = await Provable.constraintSystem(async () => { + let { accountUpdate: accountUpdateWitness } = await AccountUpdate.witness( + Empty, + async () => ({ accountUpdate, result: undefined }), + { skipCheck: true } + ); + Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); +}); +console.log( + `without all the checks on subfields, witnessing and comparing only creates ${result.rows} rows` +); + +/** + * To relate an account update to the hash which is the public input, we need to perform the hash in-circuit. + * This is takes several 100 constraints, and is basically the minimal size of a zkApp method. + */ +result = await Provable.constraintSystem(async () => { + let { accountUpdate: accountUpdateWitness } = await AccountUpdate.witness( + Empty, + async () => ({ accountUpdate, result: undefined }), + { skipCheck: true } + ); + accountUpdateWitness.hash(); +}); +console.log(`hashing a witnessed account update creates ${result.rows} rows`); diff --git a/src/examples/matrix_mul.ts b/src/examples/matrix-mul.ts similarity index 92% rename from src/examples/matrix_mul.ts rename to src/examples/matrix-mul.ts index 394a991988..8b9ee39412 100644 --- a/src/examples/matrix_mul.ts +++ b/src/examples/matrix-mul.ts @@ -1,4 +1,4 @@ -import { Field, provable, Provable } from 'snarkyjs'; +import { Field, provable, Provable } from 'o1js'; // there are two ways of specifying an n*m matrix @@ -55,9 +55,9 @@ function circuit(): Field[][] { return matrixMul(x, y); } -let { rows } = Provable.constraintSystem(circuit); +let { rows } = await Provable.constraintSystem(circuit); let result: Field[][]; -Provable.runAndCheck(() => { +await Provable.runAndCheck(() => { let result_ = circuit(); Provable.asProver(() => { result = result_.map((x) => x.map((y) => y.toConstant())); diff --git a/src/examples/nullifier.ts b/src/examples/nullifier.ts index 0df3e23d71..9693583dcf 100644 --- a/src/examples/nullifier.ts +++ b/src/examples/nullifier.ts @@ -11,20 +11,21 @@ import { MerkleMapWitness, Mina, AccountUpdate, -} from 'snarkyjs'; + Provable, +} from 'o1js'; class PayoutOnlyOnce extends SmartContract { @state(Field) nullifierRoot = State(); @state(Field) nullifierMessage = State(); - @method payout(nullifier: Nullifier) { - let nullifierRoot = this.nullifierRoot.getAndAssertEquals(); - let nullifierMessage = this.nullifierMessage.getAndAssertEquals(); + @method async payout(nullifier: Nullifier) { + let nullifierRoot = this.nullifierRoot.getAndRequireEquals(); + let nullifierMessage = this.nullifierMessage.getAndRequireEquals(); // verify the nullifier nullifier.verify([nullifierMessage]); - let nullifierWitness = Circuit.witness(MerkleMapWitness, () => + let nullifierWitness = Provable.witness(MerkleMapWitness, () => NullifierTree.getWitness(nullifier.key()) ); @@ -38,7 +39,7 @@ class PayoutOnlyOnce extends SmartContract { this.nullifierRoot.set(newRoot); // we pay out a reward - let balance = this.account.balance.getAndAssertEquals(); + let balance = this.account.balance.getAndRequireEquals(); let halfBalance = balance.div(2); // finally, we send the payout to the public key associated with the nullifier @@ -72,16 +73,16 @@ console.log('compile'); await PayoutOnlyOnce.compile(); console.log('deploy'); -let tx = await Mina.transaction(sender, () => { +let tx = await Mina.transaction(sender, async () => { let senderUpdate = AccountUpdate.fundNewAccount(sender); senderUpdate.send({ to: zkappAddress, amount: initialBalance }); - zkapp.deploy({ zkappKey }); + await zkapp.deploy(); zkapp.nullifierRoot.set(NullifierTree.getRoot()); zkapp.nullifierMessage.set(nullifierMessage); }); await tx.prove(); -await tx.sign([senderKey]).send(); +await tx.sign([senderKey, zkappKey]).send(); console.log(`zkapp balance: ${zkapp.account.balance.get().div(1e9)} MINA`); @@ -94,9 +95,9 @@ let jsonNullifier = Nullifier.createTestNullifier( console.log(jsonNullifier); console.log('pay out'); -tx = await Mina.transaction(sender, () => { +tx = await Mina.transaction(sender, async () => { AccountUpdate.fundNewAccount(sender); - zkapp.payout(Nullifier.fromJSON(jsonNullifier)); + await zkapp.payout(Nullifier.fromJSON(jsonNullifier)); }); await tx.prove(); await tx.sign([senderKey]).send(); @@ -109,8 +110,8 @@ console.log( console.log('trying second pay out'); try { - tx = await Mina.transaction(sender, () => { - zkapp.payout(Nullifier.fromJSON(jsonNullifier)); + tx = await Mina.transaction(sender, async () => { + await zkapp.payout(Nullifier.fromJSON(jsonNullifier)); }); await tx.prove(); diff --git a/src/examples/party-witness.ts b/src/examples/party-witness.ts deleted file mode 100644 index 1e380c8a49..0000000000 --- a/src/examples/party-witness.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { - AccountUpdate, - PrivateKey, - Circuit, - provable, - isReady, - Provable, -} from 'snarkyjs'; - -await isReady; - -let address = PrivateKey.random().toPublicKey(); - -let accountUpdate = AccountUpdate.defaultAccountUpdate(address); -accountUpdate.body.callDepth = 5; -accountUpdate.lazyAuthorization = { - kind: 'lazy-signature', - privateKey: PrivateKey.random(), -}; - -let fields = AccountUpdate.toFields(accountUpdate); -let aux = AccountUpdate.toAuxiliary(accountUpdate); - -let accountUpdateRaw = AccountUpdate.fromFields(fields, aux); -let json = AccountUpdate.toJSON(accountUpdateRaw); - -if (address.toBase58() !== json.body.publicKey) throw Error('fail'); - -let Null = provable(null); - -Provable.runAndCheck(() => { - let accountUpdateWitness = AccountUpdate.witness(Null, () => ({ - accountUpdate, - result: null, - })).accountUpdate; - console.assert(accountUpdateWitness.body.callDepth === 5); - Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); - Provable.assertEqual( - PrivateKey, - (accountUpdateWitness.lazyAuthorization as any).privateKey, - (accountUpdate.lazyAuthorization as any).privateKey - ); -}); - -let result = Provable.witness(() => { - let accountUpdateWitness = AccountUpdate.witness(Null, () => ({ - accountUpdate, - result: null, - })).accountUpdate; - console.assert(accountUpdateWitness.body.callDepth === 0); - Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); -}); - -console.log(`an account update has ${AccountUpdate.sizeInFields()} fields`); -console.log( - `witnessing an account update and comparing it to another one creates ${result.rows} rows` -); - -result = Provable.witness(() => { - let accountUpdateWitness = AccountUpdate.witness( - Null, - () => ({ - accountUpdate, - result: null, - }), - { skipCheck: true } - ).accountUpdate; - console.assert(accountUpdateWitness.body.callDepth === 0); - Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); -}); - -console.log( - `without all the checks on subfields, witnessing and comparing only creates ${result.rows} rows` -); diff --git a/src/examples/plain-html/index.html b/src/examples/plain-html/index.html new file mode 100644 index 0000000000..248739ad8f --- /dev/null +++ b/src/examples/plain-html/index.html @@ -0,0 +1,15 @@ + + + + + hello-o1js + + + + +
Check out the console (F12)
+ + diff --git a/src/examples/plain-html/server.js b/src/examples/plain-html/server.js new file mode 100644 index 0000000000..dc7679626c --- /dev/null +++ b/src/examples/plain-html/server.js @@ -0,0 +1,42 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import http from 'node:http'; + +const port = 8000; +const defaultHeaders = { + 'content-type': 'text/html', + 'Cross-Origin-Embedder-Policy': 'require-corp', + 'Cross-Origin-Opener-Policy': 'same-origin', +}; + +const server = http.createServer(async (req, res) => { + let file = '.' + req.url; + console.log(file); + + if (file === './') file = './index.html'; + let content; + try { + content = await fs.readFile(path.resolve('./dist/web', file), 'utf8'); + } catch (err) { + res.writeHead(404, defaultHeaders); + res.write('404'); + res.end(); + return; + } + + const extension = path.basename(file).split('.').pop(); + const contentType = { + html: 'text/html', + js: 'application/javascript', + map: 'application/json', + }[extension]; + const headers = { ...defaultHeaders, 'content-type': contentType }; + + res.writeHead(200, headers); + res.write(content); + res.end(); +}); + +server.listen(port, () => { + console.log(`Server is running on: http://localhost:${port}`); +}); diff --git a/src/examples/primitive_constraint_system.ts b/src/examples/primitive_constraint_system.ts deleted file mode 100644 index a42776d12b..0000000000 --- a/src/examples/primitive_constraint_system.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Field, Group, Poseidon, Provable, Scalar } from 'snarkyjs'; - -function mock(obj: { [K: string]: (...args: any) => void }, name: string) { - let methodKeys = Object.keys(obj); - - return { - analyzeMethods() { - let cs: Record< - string, - { - rows: number; - digest: string; - } - > = {}; - for (let key of methodKeys) { - let { rows, digest } = Provable.constraintSystem(obj[key]); - cs[key] = { - digest, - rows, - }; - } - - return cs; - }, - async compile() { - return { - verificationKey: { data: '', hash: '' }, - }; - }, - name, - digest: () => name, - }; -} - -const GroupMock = { - add() { - let g1 = Provable.witness(Group, () => Group.generator); - let g2 = Provable.witness(Group, () => Group.generator); - g1.add(g2); - }, - sub() { - let g1 = Provable.witness(Group, () => Group.generator); - let g2 = Provable.witness(Group, () => Group.generator); - g1.sub(g2); - }, - scale() { - let g1 = Provable.witness(Group, () => Group.generator); - let s = Provable.witness(Scalar, () => Scalar.from(5n)); - g1.scale(s); - }, - equals() { - let g1 = Provable.witness(Group, () => Group.generator); - let g2 = Provable.witness(Group, () => Group.generator); - g1.equals(g2).assertTrue(); - g1.equals(g2).assertFalse(); - g1.equals(g2).assertEquals(true); - g1.equals(g2).assertEquals(false); - }, - assertions() { - let g1 = Provable.witness(Group, () => Group.generator); - let g2 = Provable.witness(Group, () => Group.generator); - g1.assertEquals(g2); - }, -}; - -export const GroupCS = mock(GroupMock, 'Group Primitive'); diff --git a/src/examples/print_constraint_system.ts b/src/examples/print_constraint_system.ts deleted file mode 100644 index 2a0fe52dce..0000000000 --- a/src/examples/print_constraint_system.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - Poseidon, - Field, - Circuit, - circuitMain, - public_, - isReady, -} from 'snarkyjs'; - -class Main extends Circuit { - @circuitMain - static main(preimage: Field, @public_ hash: Field) { - Poseidon.hash([preimage]).assertEquals(hash); - } -} - -await isReady; - -console.log('generating keypair...'); -let kp = await Main.generateKeypair(); - -let cs = kp.constraintSystem(); -console.dir(cs, { depth: Infinity }); diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json deleted file mode 100644 index ac928854b5..0000000000 --- a/src/examples/regression_test.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "Voting_": { - "digest": "3d94f280d3d143b91564d659073b641b21f6e0ca8d8549c4f9f37434ebeea399", - "methods": { - "voterRegistration": { - "rows": 1260, - "digest": "9f1d08069b07c165fe425b9b1b83d3ba" - }, - "candidateRegistration": { - "rows": 1260, - "digest": "6e9e689b9d1bd2eedcc323c842cf8c87" - }, - "approveRegistrations": { - "rows": 1147, - "digest": "6189a2bf4c618401d61d30aac6328cea" - }, - "vote": { - "rows": 1675, - "digest": "ab1190db893274db9cd64d8c306d8b4a" - }, - "countVotes": { - "rows": 5797, - "digest": "cf02e1cd65980bafaf249c0444bfddf3" - } - }, - "verificationKey": { - "data": "AACd9tWcrEA7+0z2zM4uOSwj5GdeIBIROoVsS/yRuSRjKmnpZwY33yiryBLa9HQWpeZDSJI5y91gKJ9g5atltQApAhMdOuU5+NrHN3RCJtswX+WPvwaHJnihtSy2FcJPyghvBVTi2i7dtWIPQLVDIzC5ARu8f8H9JWjzjVVYE/rQLruuq2qUsCrqdVsdRaw+6OjIFeAXS6mzvrVv5iYGslg5CV5mgLBg3xC408jZJ0pe8ua2mcIEDMGEdSR/+VuhPQaqxZTJPBVhazVc1P9gRyS26SdOohL85UmEc4duqlJOOlXOFuwOT6dvoiUcdQtzuPp1pzA/LHueqm9yQG9mlT0Df8uY/A+rwM4l/ypTP/o0+5GCM9jJf9bl/z0DpGWheCJY+LZbIGeBUOpg0Gx1+KZsD9ivWJ0vxNz8zKcAS1i3FgntjqyfY+62jfTR8PW1Y4wdaFan6jSxaaH6WYnvccAo2QHxEAFL91CfnZB1pwF8NAT395N/rXr5XhMHFPoCkSHd2+5u+b62pkvFqqZZ9r24SMQOe9Bl2ZfMew2DyFLMPzwTowHw8onMEXcVKabFs9zQVp66AMf/wlipirNztdguAGC8ZsNi28yf2BHRxDtSYsD0nZCEyX8ObadimhBbX1U/tpmEvvfQ+7zalCkDE6OoMxRbf2JS3fzGzlwCcRWWeTkc4d5ts+btlepIrTet7yJK5rlsFQfJGzaeTz9BN+g+C2ZK8B+2a2Qrz386FvB+elJAkJ2/Agn35oBHB2HobDkF6sRfrXOdH5l+QV7vR2v385RKRtfnmcJeUQcpq5/JTgVwagDJ/FarTN5jFsrBBRTeW3yZ5/CfVNA7NNWxoKhjBaHVIhn/fLT5sFLYzYdCx/uTsusyZmE2d6iqnLS+j1IXNJX/zR0ZD3aGuoUc4MaFZQnN5om4dfpbloe4Roob3BuDhBHTKoYC+nVsyEvDRyiYLEOjJ45/bSwTCfwngYKtNmo3sVTvQ9mqBf0cLdBCn8skp3S/gz324TFm8iJ+t8EWlEGKTbvuT6aMce5I45FWcuUS2lIeKIq9hR0nZ1G6mxQITED8Vr59UaRCk2hAjMn8LWSbPAqQv+VEXDHIwvjvEHFZHqoGHfZ/x6BeVRLxgUZxtxzVY7PRjgRiTusnizofD6ARCB5sHEyMJTqkg0RONc0mLWuxPSg0SrdnU1oQhQkMqH5O4Df/c6DNekL1d6QYnjO0/3LMvY/f/y1+b7nPHI8+1Wqp5jZH8UsuN63SSMdfBEe6x46AG/R+YS/wH78GKekabWu9QQnUJdjXyXiqF4qRebvfcmpQz91anvVz3ggBqCv4sYqCIvP0ysDtMdi36zFErV+8SdUu+NsPDGvdPSCGdLuC25izxb21up2HORmlM5R7yuIW3rCiq8DeLD0OHjqOBZ+IEv9zEkb5fHTJvxoxnZlArtZSBpD6iIDPVDymuK+BsOggZav3K+TytjeD2Gcld5NfyRISFWUIMkZNFQRL8AQpET6RJnG1HSW0CaRfNeomtjCBWIr85wFCrp06j/D1J8B3EyhloZLJJ6ywxt41smXVugxA8LRTO+6lVBOBF14jHQCCUl6u7uiWCe1z4/bC5wQXPwWSljp8NVU8Erp1U9ModNK7W63Pkh0efvgSD5d0nLzbfa0jTdxZ1JkfKsnvYk43Ed+vmXooHZhUeZAIX8ZCizhb1Gfvm02JFwxYXmiYAOp5wkGzweU2I5zo8r5yZFI1r4XibNQs7eAfKGRv3gh8/EuLkX/bdettgPvNsI8ndpQ3kL/V8W2PQN4/hjC9AKCYBeXQG42bRncYZdLe++R2KA1ZdPDxQPF3sxUIKhzmRWqbozrtv310Maorwv6eZJjldlCJwICR9QgcDwDuNj+UFJnX3RWsdIWsUbI1T4wO0sE2sBiMX/OqmiGJEAnBegioistlFyfRvm54h+duNOl/ol1Fva7NoXvsL/wThAWUly7bnc7/Al2bBQlUrmEX46UnKXzYntkZDee7Lx1u1BBkJAj/5BH1YZOPmMCh498rBUiHmc+4uQqebqNSHdOSgC39ESss4u7GNhWj3fi9XXta6UT9wapEMGq0WTg2Kry6xNP2YZ5X8eaapRQc/KzYgz9XjQL6TKpqNuGEbRlmfYvIuoFbnOkZI7RYoGp3YheMs1pQErwOxLzZa9W3Okwx16TSDwPLR0xMdAyogMrOdKN4JSMyNnmOaoVf6PkN+K9fz7RuHtvgjKpuz4vsK5Z2wRneqPrnfu6PkgHcRQrd0SxqCbN23Z/yp8qOcN6XU49iCNEBjztT00tolQ9hCPMSE/eTZ+ioez7m3pJFVks3T5Rk/e+6MeowJWIOv20x6CPS9mhpr1JPwdNFrWdgs19VsobntCpF/rWxksdrYyk=", - "hash": "3937169542001515602242969811817979001997698429197375219350451752328949933498" - } - }, - "Membership_": { - "digest": "110319a1c90f943e63a60a60b414cb4c5c807238a108f221127cf1470253e8b9", - "methods": { - "addEntry": { - "rows": 1354, - "digest": "f44d9087799be73eb45d70624acbe2e5" - }, - "isMember": { - "rows": 470, - "digest": "dc1d26c7e30d3e14bd0d562685846026" - }, - "publish": { - "rows": 695, - "digest": "358a5f723f262cc3a429632a08366782" - } - }, - "verificationKey": { - "data": "AACwuS3vTWCwpRIX/QlJQqJcmPO9nPm4+sCfcrqiY1NUMiV9k6Pc8kFkMsbGLst78T8uAnYwc1Ql49kq0I2GizwshS9xkBcfxRTAAMBHXhf8KDkK39AalVocKIrfWMV0MSShinj0bCxPCc10K0cya4Voy8fud4+hktDOuwjaAstpEJSbKRHMIki77xHmJWlFUYdkgPg30MU4Ta3ev/h+mcMWmofyhLSQqUbaV6hM95n3Y0Wcn2LRNxJP8TRwHndIcylleqPsGMh3P+A+N9c32N4kl29nreMJJdcUrCXK90GLPAFOB9mHIjKk9+9o3eZc3cGQ+jppXoN3zwO91DeT/GYvXqCZTAudLxIwuJU11UBThG5CKKABa9ulQ1bYGXj9Eydy0vPxfojDeFrnKMi9GKSjiSMzmOLbIw7Dt+g9ggjsHM5rPrT7dY1VV4ZT9shjlcX3029xnk3Bjz4Q9PiK+A8o6f7L6aVB07I+QY2iDtwSQWuXYPohrk85I1UbPfY+giWqFXBtHaN45PMWCyBx0TKaozETCmv0kA5KGTzesYQCECPQ8F2DM+oXz8xly+z9/Ypt/Zx9NvF7wute/1s6Q/QuAIr42ADzPs0nb/gJOcn4pMrwSK7mAj9QkI064DoZ2dgNYI1VtC1L/9Ffb7/YrFy5fKNsMFDNJPDKHzt4+MejQha2vXswFt90jphf6jgLtFJULrvKVg+YCMNM/04QLTGcMmjjzv4LciQ6IVXth7zhVKxfL1/2peC0r/ZrP8k+Ox4LEBXWMCQE5kfK476bQgrLeKJfQ45PZfgB688DGwaYAxWbcxBV822/aAsA55ijFY1Xf7S+DiytY4a/u0bellKMDUQqTOq9VwmbDv868zXscUwKpNVR3wy2En/q9M/HJJc4BZyuuQvlQSR59m0gL4hKHf5Dci/YVvM6ACHmg+5SxCr1pUNKbyy2lsIa5Ma40ZmsTpT4/lQczmGENQSQXA9bFibT0Q+Vj885p9heLOCCXyAujC4DhAdYmT1MQ7v4Ixcke0m6tWrQu0g8nRyy9bdPm5zgCFMbkMKyUZacj1AHeTNmuHzCOdENav7XgwXiZ87tCUlHj2A5HC2k76gS15CMPBaQEGLxiOi2dExA8cYTomPefmXtDJIQR9RaxRxAudIGgaQnPvzvueiYNUIsb0irHc/n1uIucFOxFJGaUhGJzSv6l24QrqQAp0ebGEbpXqv21bhlr6dYBsculE2VU9SuGJ2g6yuuKf4+lfJ2V5TkIxFvlgw5cxTXNQ010JYug38++ZDV+MibXPzg+cODE5wfZ3jon5wVNkAiG642DzXzNj67x80zBWLdt3UKnFZs9dpa1fYpTjlJg8T+dnJJiKf2IvmvF8xyi1HAwAFyhDL2dn/w/pDE2Kl9QdpZpQYDEBQgCCkegsZszQ+2mjxU9pLXzz5GSoqz8jABW5Qo3abBAhvYKKaAs6NoRgeAD6SadFDbQmXaftE+Y1MVOtjnaZDUBdwahWiJMlkfZpxW1aubEc/GSX8WzCZ8h9HeakcRc7kcN0CR8kmfER3eiZ2JMbt5cQl/afNjwGGAmeXzTaR34AgFjiw/RlZJkhYm9jyf18M8yP94QGBMxd6Y6wrNvOmJHzEnp8aitJsDlZklm8LKbjumlSbLcbBokpIDhFBBKfwP2qsQX7eHLCZ/3mztoFKoIiYXgrHWG8m2SzIJ/ljn6Rg7AxIsPjzZyEw1eXAOC7A1FCT/757ygMsnk+rLlpDTBYLmhJtQdt61MQFDi5BuCmQ/PY9C/74/k4APl5htiNcCZty/1JElFwjuCQFjvAiMPUMyqp7/ALFapsTZqhSs1g6jd8uhuJoTNEqLDvKUUbs0kMvGy8BOG0YXNxmNccabGwBzxmijv6LF/Xinecl4aD8FCh6opY98TJnOHd3XSYL1DbLqmmc6CXEM+g5iDGnXr/CkI2Jy37OkF8X03jz4AH0Yj0+J63yH4IS+PrNpKZEXKh7PvXNaLGGKsFcKEi63/xKPKH0G4RzvFKbkp+IWqtIYjMiwIJMwzmfS1NLLXqqpFiD364eFcXINR2rrDKcoTUp1JkVZVfXfKwaRUPWSGFYIYMtwPh2w8ZfubAmXZFpyzstORhFyg9rtVAAy0lcDhQwWVlhFFkR2qbdoy0EFLBrfKqUIkd1N6vDQQYL1RGaTAv/ybregrJsFo+VP3ZatlR6LnKYWp1m7vPkGm3I6Pus/mvp1k10QGk8nhFuR31DjsG3lzZ4gXSs1oSv0qbxD2S6g5+Y6cPbITEGX3uQjsunXnQ9PHd22Mk+fqbDakTiCJh6aFqqPNShiAXkGSuC1oXJHX3zqnbn75dWO0UVhBNAbjYkSnQeyka1wnZb12sR+PlRMvWQVcd93t5L/FiE0ORo=", - "hash": "9996223880170732158764822649803321417212830666367673158223414893469449014464" - } - }, - "HelloWorld": { - "digest": "1f5a4490d9660960367eba05253fb1ee5a8c19ec4cfaa3808a0a3bb021fb49e4", - "methods": { - "update": { - "rows": 2352, - "digest": "5fd358199036f97e4d5dd88b8c90f644" - } - }, - "verificationKey": { - "data": "AAAxHIvaXF+vRj2/+pyAfE6U29d1K5GmGbhiKR9lTC6LJ2o1ygGxXERl1oQh6DBxf/hDUD0HOeg/JajCp3V6b5wytil2mfx8v2DB5RuNQ7VxJWkha0TSnJJsOl0FxhjldBbOY3tUZzZxHpPhHOKHz/ZAXRYFIsf2x+7boXC0iPurETHN7j5IevHIgf2fSW8WgHZYn83hpVI33LBdN1pIbUc7oWAUQVmmgp04jRqTCYK1oNg+Y9DeIuT4EVbp/yN7eS7Ay8ahic2sSAZvtn08MdRyk/jm2cLlJbeAAad6Xyz/H9l7JrkbVwDMMPxvHVHs27tNoJCzIlrRzB7pg3ju9aQOu4h3thDr+WSgFQWKvcRPeL7f3TFjIr8WZ2457RgMcTwXwORKbqJCcyKVNOE+FlNwVkOKER+WIpC0OlgGuayPFwQQkbb91jaRlJvahfwkbF2+AJmDnavmNpop9T+/Xak1adXIrsRPeOjC+qIKxIbGimoMOoYzYlevKA80LnJ7HC0IxR+yNLvoSYxDDPNRD+OCCxk5lM2h8IDUiCNWH4FZNJ+doiigKjyZlu/xZ7jHcX7qibu/32KFTX85DPSkQM8dAO0Hj33/8TUiUYdhgVdE9P0LVEmp8IYdjYpjhvqR1Jg8pDEm6cTcMITtrfv8/iuFZo+LtEKklQFjF4XafXoG2goKR89XcqLS/NP7lwCEej/L8q8R7sKGMCXmgFYluWH4JBSPDgvMxScfjFS33oBNb7po8cLnAORzohXoYTSgztklD0mKn6EegLbkLtwwr9ObsLz3m7fp/3wkNWFRkY5xzSZN1VybbQbmpyQNCpxd/kdDsvlszqlowkyC8HnKbhnvE0Mrz3ZIk4vSs/UGBSXAoESFCFCPcTq11TCOhE5rumMJErv5LusDHJgrBtQUMibLU9A1YbF7SPDAR2QZd0yx3wbCC54QZ2t+mZ4s6RQndfRpndXoIZJgari62jHRccBnGpRmURHG20jukwW6RYDDED7OlvEzEhFlsXyViehlSn4EFMievUfTY7TQp0kvgvOGfy4ofGbx4qUB3k+DUHuWDh+E6fT9DGrz3eeQEMngOiULsqgKiNA4AtNPPLE1FXdZCkexIlapLarEGI7ZVvg5jAqXDlXxVS3HRo/skxgt2LYm8wLIKLHX0ClznArLVLXkSX18cSoSsVMG3QCSsmH1Oh8xOGUbSHzawovjubcH7qWjIZoghZJ16QB1c0ryiAfHB48OHhs2p/JZWz8Dp7kfcPkeg2Of2NbupJlNVMLIH4IGWaPAscBRkZ+F4oLqOhJ5as7fAzzU8PQdeZi0YgssGDJVmNEHP61I16KZNcxQqR0EUVwhyMmYmpVjvtfhHi/6I3mfPS+FDxTuf4yaqVF0xg2V3ep/WYnnKPJIegxoTFY8pChjyow3PMfhAP5HOnXjHQ2Va9BFo4mfEQXvRzPmIRRVmlVsP8zA+xuHylyiww/Lercce7cq0YA5PtYS3ge9IDYwXckBUXb5ikD3alrrv5mvMu6itB7ix2f8lbiF9Fkmc4Bk2ycIWXJDCuBN+2sTFqzUeoT6xY8XWaOcnDvqOgSm/CCSv38umiOE2jEpsKYxhRc6W70UJkrzd3hr2DiSF1I2B+krpUVK1GeOdCLC5sl7YPzk+pF8183uI9wse6UTlqIiroKqsggzLBy/IjAfxS0BxFy5zywXqp+NogFkoTEJmR5MaqOkPfap+OsD1lGScY6+X4WW/HqCWrmA3ZTqDGngQMTGXLCtl6IS/cQpihS1NRbNqOtKTaCB9COQu0oz6RivBlywuaj3MKUdmbQ2gVDj+SGQItCNaXawyPSBjB9VT+68SoJVySQsYPCuEZCb0V/40n/a7RAbyrnNjP+2HwD7p27Pl1RSzqq35xiPdnycD1UeEPLpx/ON65mYCkn+KLQZmkqPio+vA2KmJngWTx+ol4rVFimGm76VT0xCFDsu2K0YX0yoLNH4u2XfmT9NR8gGfkVRCnnNjlbgHQmEwC75+GmEJ5DjD3d+s6IXTQ60MHvxbTHHlnfmPbgKn2SAI0uVoewKC9GyK6dSaboLw3C48jl0E2kyc+7umhCk3kEeWmt//GSjRNhoq+B+mynXiOtgFs/Am2v1TBjSb+6tcijsf5tFJmeGxlCjJnTdNWBkSHpMoo6OFkkpA6/FBAUHLSM7Yv8oYyd0GtwF5cCwQ6aRTbl9oG/mUn5Q92OnDMQcUjpgEho0Dcp2OqZyyxqQSPrbIIZZQrS2HkxBgjcfcSTuSHo7ONqlRjLUpO5yS95VLGXBLLHuCiIMGT+DW6DoJRtRIS+JieVWBoX0YsWgYInXrVlWUv6gDng5AyVFkUIFwZk7/3mVAgvXO83ArVKA4S747jT60w5bgV4Jy55slDM=", - "hash": "5137663734069454542185980113506904745018869331949819257646538371828146355743" - } - }, - "TokenContract": { - "digest": "1626d65d9dddb02c2efb061ccd86dde1be0eb2da1f6d2a9d5fc1dd67f6a13a69", - "methods": { - "init": { - "rows": 656, - "digest": "69166c148367fb957d6ee9ccc88e32bf" - }, - "init2": { - "rows": 653, - "digest": "d37b3b719bbc6bafe7569bf00867d45d" - }, - "deployZkapp": { - "rows": 703, - "digest": "e9f389959f9e151a157e067457f65ad9" - }, - "approveUpdate": { - "rows": 1944, - "digest": "4c139968037dddff3d5b96b2c2b7f7d6" - }, - "approveAny": { - "rows": 1943, - "digest": "10540506937a8e900702c11166a28851" - }, - "approveUpdateAndSend": { - "rows": 2325, - "digest": "6f3a192b42c9b9543833d1419f1cc5fa" - }, - "transferToAddress": { - "rows": 1045, - "digest": "5563c415f4cbc4e55deee27b28cc2b36" - }, - "transferToUpdate": { - "rows": 2330, - "digest": "c01ade54a6dd1c2b8727cabdf6bdb239" - }, - "getBalance": { - "rows": 687, - "digest": "709f06f2e08cfe43ddf3ccbd60e642c8" - } - }, - "verificationKey": { - "data": "AAAVRdJJF0DehjdPSA0kYGZTkzSfoEaHqDprP5lbtp+BLeGqblAzBabKYB+hRBo7ijFWFnIHV4LwvOlCtrAhNtk/Ae0EY5Tlufvf2snnstKNDXVgcRc/zNAaS5iW43PYqQnEYsaesXs/y5DeeEaFxwdyujsHSK/UaltNLsCc34RKG71O/TGRVVX/eYb8saPPV9W5YjPHLQdhqcHRU6Qq7hMEI1ejTXMokQcurz7jtYU/P56OYekAREejgrEV38U82BbgJigOmh5NhgGTBSAhJ35c9XCsJldUMd5xZiua9cWxGOHm0r7TkcCrV9CEPm5sT7sP7IYQ5dnSdPoi/sy7moUPRitxw7iGvewRVXro6rIemmbxNSzKXWprnl6ewrB2HTppMUEZRp7zYkFIaNDHpvdw4dvjX6K/i527/jwX0JL4BideRc+z3FNhj1VBSHhhvMzwFW6aUwSmWC4UCuwDBokkkBtUE0YYH8kwFnMoWWAlDzHekrxaVmxWRS0lvkr8IDlsR5kyq8SMXFLgKJjoFr6HZWE4tkO/abEgrsK1A3c9F5r/G2yUdMQZu8JMwxUY5qw7D09IPsUQ63c5/CJpea8PAB3zSpZOqq9K0varn5pYvXqxhJYxiO2rHTysAR/7qgsXynBHAsYiBoTGE0/ukXEs0hwc71AGMirQWaB6JNd0ZjNU3betWNXGJbS4dC4hTNfWM956bK+fwkIlwhM3BC+wOai+M0+y9/y/RSI8qJkSU3MqOF9+nrifKRyNQ3KILqIyR7LjE0/Z/4NzH7eF3uZTBlqfLdf8WhXdwvOPoP1dCx1shF6g4Hh9V4myikRZBtkix1cO5FLUNLNAFw+glg1PB1eA+4ATFuFcfMjxDpDjxqCFCyuQ5TaLuNfYMA7fiO0vB6yqtWgSmCOlD/MQqAhHYRMq4PXk3TUQSle8XBZ67T0+gENjIJleTRgZFG6PgIEwHXcsKIvfFAPklTlnY+5sNVw8yBisVaFgw36DrHWNavWvsZM5HwD0h1Wk0hkavjEISQ0rrnnd/AfgunBqch1m3VVEzh4/K1aoUlCxT8lmfwtGqLwn/lT/ZCRmcUiqpSkctvoz+oc1nq3kunmlomk7BP3UaghDqb+UJVi+sS4ywECcuvZE47Yveb52wMUUmEwNVvPjH7aukOusSSc2lRwM0h2ohrjZYap6NZHwtOHAWAKav/WbabGDMJhbugO4TNu1/i5omH8pbsjGGHQXk1UYPoP1SnMVPZ9RXPoWHJn/kePU9QqGxETHF4T7b2Ov7CcZDLuz147VCknmGiziHzbmYJleu4tzSlFsxHPkp2d9JiDUbO7X66Dh/+84gc5KWpMnEIAF9gITi3cXUglZTjWaASaXcpgHXXGZHZJcrG2VfPNjgTKJ1+CbvyXlvuhvX+0E2oaPB+BoP0i2iTXQHPNhOY/Gg2h6uKvE5fSSiYC7Rws2TGF1aEM54wX3Ti1qA1cAiNG5y8yk1YMGCk3TPqs9MRp0qjgjJbbvFlbgPkkqz5o6c7g8gfhIa4VEJyyI2joqJeIc7vMZFWhquSFHNs0TZKvKLiSAsyNDrpWZb/1PHxziswKvisk296AJi7hmlM1pKx6S4LlbT2OKLXbgq5HUKfe8QhxG4aOsPSSiVGwvnCrIPdSxLq77M27UWXnXHC8mmJmOsGUFj+bdX/u6AgrBhw/w74dDbuNEpC80PbJTuglF/TeDryYsFWCrBnF/WPstgzy3zDDTZ3DXHVYVxOEvErIynlQEY9Cv9QSxRI3dA+hLtob/L78ZeJSU4Al+Qv0QGZTOxQORosVshOP2eFQ1VMKGWOpCVvyi8QE4fa+gOgYT0JRm4rkQBZ5WDlYGkamD3euC92Kd7Z39G89h/AqeFACahkAW1a78SzLW69mZ+CDLfKp/xQsi2TWgJqGh7QNOEtMnn/2owLzLWd071mvUtT0484Eqx6hUqLJMH70p8oUjQIMsh0mvp1BWSU8XC6z+UZIpVm2CERrV8BMLmTLOgTNJlEIJQR7zzpJCDFNNOI+Y2ZtdcuU8XHgcsQhQ3PgCACFAWN3rO+goXoTWdYR/LcqszKzPnMArmPIHWkRM6Mkm13OsHXCVudUbqQjC/pNQZH1VW+RMXnre1vQVb3fnCy5h28Dce3Q2WzjBSZFhe3iADZpo7gWHM/sqe+Mbnbn8A+RRWVNbtjss9376jN73zV4xPH3un3VjTxrzCluqR8MbH8t7mhPBqV5CslmSIbDNruVXtwCf4VS1nssw63PfLzeOSvzhTTsg82rna/+TKl1RIwhD8VFnCDq/Rk8fdy/+K5qP6GcSTbh6J8ERx4jOOukL9TUCpJkhvo/3ED8GOewmWAwzL8avXuf9AFvhwH3ENp5v4IIGBljuDJ77vckGmTI=", - "hash": "12366494402222742422656395986651384766285850064146879115569953837710228942559" - } - }, - "Dex": { - "digest": "3d52c586067f910c9e2d2c4845f87bc8a94ecae0f29173925a3c84ac792fb4c9", - "methods": { - "supplyLiquidityBase": { - "rows": 3754, - "digest": "94cc0fcf15c730ce5985eab050af69ee" - }, - "swapX": { - "rows": 1987, - "digest": "b19ba33a99fce487fa57a26d4f94acef" - }, - "swapY": { - "rows": 1987, - "digest": "4de0b8ee16a3d16c1c35ca35b7afb5b3" - }, - "burnLiquidity": { - "rows": 719, - "digest": "2efa6087ee0f835cd54af2f2be520738" - }, - "transfer": { - "rows": 1045, - "digest": "f884b397fd90eb23d5687d9540fd2ba9" - } - }, - "verificationKey": { - "data": "AADgDFCYyznG8hH/Z695+WW86B544SmJFzz5ObrizTJ4KMqy+pfsOR2Mt2yGViXSJPpAR76RNHNga83UB8/9OPQIB+uHOnxXH7vN8sUeDQi50gWdXzRlzSS1jsT9t+XsQwHNWgMQp04pKmF+0clYz1zwOO95BwHGcQ/olrSYW4tbJN6KW0hN2eESQfUJcwfB6uUzwvGtkFs+aiUykn7KUgUgXQkKgdHHdyFioNHNPmkpiAre/Ts8BKwwvf5hCa1MtBF6ax6ymlATB4YBL0ETiEPTE/Qk1zGWUSL2UB6aY45/LlfTLCKlyLq7cR3HOucFfBncVfzI7D8j5n4wVqY+vAI4cf+Yv7iVRLbeFcycXtsuPQntgBzKa/mcqcWuVM7p2SYRrtKdX8EKvOO6NhfLx4x0atAi8pKf+vZR76LSP4iOA8hwXvk6MNvPt1fxCS96ZAKuAzZnAcK+MH1OcKeLj+EHtZmf40WRb3AEG5TWRKuD6DT5noDclZsE8ROZKUSOKAUGIBvt7MpzOWPPchmnromWEevmXo3GoPUZCKnWX6ZLAtJwAszLUgiVS8rx3JnLXuXrtcVFto5FFQhwSHZyzuYZACSDvhRxuM+5L2Qth8mnMH8BTZQTJQx/6LLKDAobJrkY9u6ldxz7eBxiYzGyHItdl7shIiRXeksUh5qQZRIpngOQL0kMluTF8LDFuJ5vyF99EbaStETc+AjKGlOkBt5cD45qla/CLlrYYXIMWtgmoNr+7YXF4+3zLKroOdvy+GAGJ5M/KjfmCc2/EsnV7Mhax350ZtrXdzh/HWIWzEZKKxcbERFbRtf+fkMOOLNpNov1FEFvKOU612vDOIbrVHeBN9mwuepUrJctcfgLc0Mi3Sxs3+NA0I74qm5ktjmplDwgUtKzIs3IrVFv6b1pg/J32HmwNzJZw2fYzpFE1LDjBSK/SX3axwMy5yEd8+jl4uAdQZpa9UQQIHu1Y1ZMgJSDDicXz6D1bZMA1Q2/lU+8AYbldgQVmlLq/lzr63krX+AMWJ/N/5LgZn+yH/y0p4SYun4SMjKMwMqs/RHbHS60+j9T8Em79LBkeQnpymuv1/EiSJO/v3XiJLAnfMqtpO4MKUPs9thJFQniS9QQWGbtAixPrKdLzz2b2EAPHQZBgy8RXgu3SgmJZ7kOVADQO85e32b/+MPBihMC4kExSJnQ+gn59l19FcR35ItoigIxtMfkv3rdlCOeBVI93oVl5esiH8AvYGHhulWIvrNfKol3Viir41zv4qMBOcQg8+ygqjwqREU5+qiYeJlQ2AtT0/PVeZWg4mHC39uz1Lld3N2hyyxRo+Z0nC/8220uuf9gAnQ+JFixgyYW0NowUtuFj+uYAV9Dh/Zpe4LyAOkU0kBW4CEuOxNr+gz+9h0BoPfBHlMuuQAUc5L8uMunJC7uBKZiL+/tT1ZGfyIuqU47fEP9Hghxmip8v7gpf+4wB0MVUUwav9QRe9g88ER1HcJPqYb4EIOc2kbYSX75bT0mAFqR8lwZrj6lbQtNS0QQboG5fzoyYGi8YnSXhC2T5fFDpGJ319GHUsna58o5wk8LMwKWNTxq+FN6XiRgu0BFOrtG6MtT1OxYE9Dti6WatGDsWv+KMLDHjxUK1bhiSRnvkWYNcnuDJ0Ry+PRGHNUijVU0SbchntC2JHdhwKbwIofwKHE8HhvlK8FgQ1VOLDioA26UFzr23LpCTqwSJ7/sAqttNGcPR8MSeeR9TQvXNYQPKrA7Gh720X+7LD6BuHdy4vkcr9EKBU0ccUJ2ABBiyPdji+AgEbUCL/wrp6/GX8pui5YJGWx3XmIFj/RnYS2Je5FZ7w74JclD3XhLUo5Dhpq5RznHplpLB9mNdZdm5269US/XCgC/ZKyUxW3+0ajdBY1cLzF6qglitaYTp3MVUENVOkACM2RyKw6jIK2Leq3qLp6AUz21VXj4WznZcdI8MXqT9v8HxjXbAI9dtbhLRZRpJmu/129vrVmwSTHvsVoA7vXyYh/iO3ZMcy+D1x+HZU6Q/oDYCicqOPHxpSc9QGehmNyeGzI//524Gz3RudkU7s6MPdLWqZrieRTnWsTIrCDieu4ValfP8BFz7asYUv0t9jMWpv3yjbY7c5h8N/m7IUXwTQCzFpjPV7HC72BjVwPaYqh5/oAQsSNcv5I3c2GsCGj5C4hFFoT7eWfVtu/6ibQl0COhRDsegnOBtZ7NGfybI8IIO/4yrgel92bypb3eSxeMvdE5wzURluGDkBVVIACD8C5W1MzqrejUiiTfc3mkLhQ0xKRRhT0qqkmYWlbGN5hmMOA9YaYx8OFTgMys1WbzdidWgEkyvvdkWctGlges6eg/lJE61tJ8wGxvJfKtpyDW/2MRvsnO1+2EXIQ2eV3hkxg=", - "hash": "25368023113388663131248907317546981854837405077704880729120589306824127621207" - } - }, - "Group Primitive": { - "digest": "Group Primitive", - "methods": { - "add": { - "rows": 33, - "digest": "4e687eaee46e4c03860f46ceb2e50bd3" - }, - "sub": { - "rows": 34, - "digest": "dc048f79ddfd96938d439b8960d42d57" - }, - "scale": { - "rows": 114, - "digest": "198a5c73fb635db3d7ca134c02687aba" - }, - "equals": { - "rows": 42, - "digest": "bca7af6eb9762989838d484c8e29a205" - }, - "assertions": { - "rows": 20, - "digest": "d4525ed2ab7b897ab5d9e8309a2fdba2" - } - }, - "verificationKey": { - "data": "", - "hash": "" - } - } -} \ No newline at end of file diff --git a/src/examples/schnorr_sign.ts b/src/examples/schnorr_sign.ts deleted file mode 100644 index 435c52cbe0..0000000000 --- a/src/examples/schnorr_sign.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - Field, - Scalar, - Group, - Circuit, - public_, - circuitMain, - prop, - CircuitValue, - Signature, - isReady, -} from 'snarkyjs'; - -await isReady; - -class Witness extends CircuitValue { - @prop signature: Signature; - @prop acc: Group; - @prop r: Scalar; -} - -// Public input: -// [newAcc: curve_point] -// Prove: -// I know [prevAcc: curve_point] and [signature : Signature] such that -// the signature verifies against Brave's public key and [newAcc] is -// a re-randomization of [prevAcc] - -export class Main extends Circuit { - @circuitMain - static main(w: Witness, @public_ newAcc: Group) { - let H = new Group({ x: -1, y: 2 }); - let r: Scalar = Provable.witness(Scalar, () => w.r); - let mask = H.scale(r); - let prevAcc: Group = Provable.witness(Group, () => w.acc); - let pubKey = Group.generator; // TODO: some literal group element - let signature = Provable.witness(Signature, () => w.signature); - //signature.verify(pubKey, [prevAcc.x, prevAcc.y, signature.r]).assertEquals(true); - prevAcc.add(mask).assertEquals(newAcc); - } -} - -class Circ extends Circuit { - @circuitMain - static main(@public_ x: Field) { - let acc = x; - for (let i = 0; i < 1000; ++i) { - acc = acc.mul(acc); - } - } -} - -function testSigning() { - const _msg = [Field.random()]; - const privKey = Scalar.random(); - const _pubKey = Group.generator.scale(privKey); - //const s = Signature.create(privKey, msg); - //console.log('signing worked', s.verify(pubKey, msg).toBoolean()); -} - -export function main() { - const before = new Date(); - const kp = Circ.generateKeypair(); - const after = new Date(); - testSigning(); - console.log('keypairgen', after.getTime() - before.getTime()); - console.log('random', Field.random()); - const proof = Circ.prove([], [new Field(2)], kp); - console.log(proof, kp); - let ok = Circ.verify([Field(2)], kp.verificationKey(), proof); - console.log('verified', ok); -} diff --git a/src/examples/simple_zkapp_berkeley.ts b/src/examples/simple-zkapp-berkeley.ts similarity index 92% rename from src/examples/simple_zkapp_berkeley.ts rename to src/examples/simple-zkapp-berkeley.ts index e749219710..5bedd28f7f 100644 --- a/src/examples/simple_zkapp_berkeley.ts +++ b/src/examples/simple-zkapp-berkeley.ts @@ -1,5 +1,5 @@ /** - * This is an example for interacting with the Berkeley QANet, directly from snarkyjs. + * This is an example for interacting with the Berkeley QANet, directly from o1js. * * At a high level, it does the following: * -) try fetching the account corresponding to the `zkappAddress` from chain @@ -16,13 +16,8 @@ import { SmartContract, Mina, AccountUpdate, - isReady, - shutdown, - DeployArgs, fetchAccount, -} from 'snarkyjs'; - -await isReady; +} from 'o1js'; // a very simple SmartContract class SimpleZkapp extends SmartContract { @@ -33,9 +28,8 @@ class SimpleZkapp extends SmartContract { this.x.set(initialState); } - @method update(y: Field) { - let x = this.x.get(); - this.x.assertEquals(x); + @method async update(y: Field) { + let x = this.x.getAndRequireEquals(); y.assertGreaterThan(0); this.x.set(x.add(y)); } @@ -83,9 +77,9 @@ if (!isDeployed) { // the `transaction()` interface is the same as when testing with a local blockchain let transaction = await Mina.transaction( { sender: feePayerAddress, fee: transactionFee }, - () => { + async () => { AccountUpdate.fundNewAccount(feePayerAddress); - zkapp.deploy({ verificationKey }); + await zkapp.deploy({ verificationKey }); } ); // if you want to inspect the transaction, you can print it out: @@ -102,9 +96,7 @@ if (isDeployed) { console.log(`Found deployed zkapp, updating state ${x} -> ${x.add(10)}.`); let transaction = await Mina.transaction( { sender: feePayerAddress, fee: transactionFee }, - () => { - zkapp.update(Field(10)); - } + () => zkapp.update(Field(10)) ); // fill in the proof - this can take a while... console.log('Creating an execution proof...'); @@ -117,5 +109,3 @@ if (isDeployed) { console.log('Sending the transaction...'); await transaction.sign([feePayerKey]).send(); } - -shutdown(); diff --git a/src/examples/simple_zkapp.js b/src/examples/simple-zkapp.js similarity index 87% rename from src/examples/simple_zkapp.js rename to src/examples/simple-zkapp.js index 0a097a2af6..108fd0913a 100644 --- a/src/examples/simple_zkapp.js +++ b/src/examples/simple-zkapp.js @@ -1,5 +1,5 @@ /** - * Demonstrates how to use snarkyjs in pure JavaScript + * Demonstrates how to use o1js in pure JavaScript * * Decorators `@method` and `@state` are replaced by `declareState` and `declareMethods`. */ @@ -10,13 +10,9 @@ import { SmartContract, Mina, AccountUpdate, - isReady, declareState, declareMethods, - shutdown, -} from 'snarkyjs'; - -await isReady; +} from 'o1js'; class SimpleZkapp extends SmartContract { constructor(address) { @@ -31,7 +27,7 @@ class SimpleZkapp extends SmartContract { this.x.set(initialState); } - update(y) { + async update(y) { this.emitEvent('update', y); this.emitEvent('update', y); this.account.balance.assertEquals(this.account.balance.get()); @@ -59,9 +55,9 @@ console.log('compile'); await SimpleZkapp.compile(); console.log('deploy'); -let tx = await Mina.transaction(feePayer, () => { +let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); + await zkapp.deploy(); }); await tx.sign([feePayerKey, zkappKey]).send(); @@ -72,5 +68,3 @@ tx = await Mina.transaction(feePayer, () => zkapp.update(Field(3))); await tx.prove(); await tx.sign([feePayerKey]).send(); console.log('final state: ' + zkapp.x.get()); - -shutdown(); diff --git a/src/examples/simple_zkapp.ts b/src/examples/simple-zkapp.ts similarity index 74% rename from src/examples/simple_zkapp.ts rename to src/examples/simple-zkapp.ts index f7351b4df1..4589ab2a6c 100644 --- a/src/examples/simple_zkapp.ts +++ b/src/examples/simple-zkapp.ts @@ -10,8 +10,8 @@ import { AccountUpdate, Bool, PublicKey, -} from 'snarkyjs'; -import { getProfiler } from './profiler.js'; +} from 'o1js'; +import { getProfiler } from './utils/profiler.js'; const doProofs = true; @@ -22,17 +22,19 @@ class SimpleZkapp extends SmartContract { events = { update: Field, payout: UInt64, payoutReceiver: PublicKey }; - @method init() { + @method + async init() { super.init(); this.x.set(initialState); } - @method update(y: Field): Field { - this.account.provedState.assertEquals(Bool(true)); - this.network.timestamp.assertBetween(beforeGenesis, UInt64.MAXINT()); + @method.returns(Field) + async update(y: Field) { + this.account.provedState.requireEquals(Bool(true)); + this.network.timestamp.requireBetween(beforeGenesis, UInt64.MAXINT()); this.emitEvent('update', y); let x = this.x.get(); - this.x.assertEquals(x); + this.x.requireEquals(x); let newX = x.add(y); this.x.set(newX); return newX; @@ -42,8 +44,9 @@ class SimpleZkapp extends SmartContract { * This method allows a certain privileged account to claim half of the zkapp balance, but only once * @param caller the privileged account */ - @method payout(caller: PrivateKey) { - this.account.provedState.assertEquals(Bool(true)); + @method + async payout(caller: PrivateKey) { + this.account.provedState.requireEquals(Bool(true)); // check that caller is the privileged account let callerAddress = caller.toPublicKey(); @@ -51,10 +54,10 @@ class SimpleZkapp extends SmartContract { // assert that the caller account is new - this way, payout can only happen once let callerAccountUpdate = AccountUpdate.create(callerAddress); - callerAccountUpdate.account.isNew.assertEquals(Bool(true)); + callerAccountUpdate.account.isNew.requireEquals(Bool(true)); // pay out half of the zkapp balance to the caller let balance = this.account.balance.get(); - this.account.balance.assertEquals(balance); + this.account.balance.requireEquals(balance); let halfBalance = balance.div(2); this.send({ to: callerAccountUpdate, amount: halfBalance }); @@ -77,7 +80,9 @@ let zkappKey = PrivateKey.random(); let zkappAddress = zkappKey.toPublicKey(); // a special account that is allowed to pull out half of the zkapp balance, once -let privilegedKey = PrivateKey.random(); +let privilegedKey = PrivateKey.fromBase58( + 'EKEeoESE2A41YQnSht9f7mjiKpJSeZ4jnfHXYatYi8xJdYSxWBep' +); let privilegedAddress = privilegedKey.toPublicKey(); let initialBalance = 10_000_000_000; @@ -86,17 +91,21 @@ let zkapp = new SimpleZkapp(zkappAddress); if (doProofs) { console.log('compile'); + console.time('compile'); await SimpleZkapp.compile(); + console.timeEnd('compile'); +} else { + await SimpleZkapp.analyzeMethods(); } console.log('deploy'); -let tx = await Mina.transaction(sender, () => { +let tx = await Mina.transaction(sender, async () => { let senderUpdate = AccountUpdate.fundNewAccount(sender); senderUpdate.send({ to: zkappAddress, amount: initialBalance }); - zkapp.deploy({ zkappKey }); + zkapp.deploy(); }); await tx.prove(); -await tx.sign([senderKey]).send(); +await tx.sign([senderKey, zkappKey]).send(); console.log('initial state: ' + zkapp.x.get()); console.log(`initial balance: ${zkapp.account.balance.get().div(1e9)} MINA`); @@ -105,24 +114,24 @@ let account = Mina.getAccount(zkappAddress); console.log('account state is proved:', account.zkapp?.provedState.toBoolean()); console.log('update'); -tx = await Mina.transaction(sender, () => { - zkapp.update(Field(3)); +tx = await Mina.transaction(sender, async () => { + await zkapp.update(Field(3)); }); await tx.prove(); await tx.sign([senderKey]).send(); // pay more into the zkapp -- this doesn't need a proof console.log('receive'); -tx = await Mina.transaction(sender, () => { +tx = await Mina.transaction(sender, async () => { let payerAccountUpdate = AccountUpdate.createSigned(sender); payerAccountUpdate.send({ to: zkappAddress, amount: UInt64.from(8e9) }); }); await tx.sign([senderKey]).send(); console.log('payout'); -tx = await Mina.transaction(sender, () => { +tx = await Mina.transaction(sender, async () => { AccountUpdate.fundNewAccount(sender); - zkapp.payout(privilegedKey); + await zkapp.payout(privilegedKey); }); await tx.prove(); await tx.sign([senderKey]).send(); @@ -132,8 +141,8 @@ console.log('final state: ' + zkapp.x.get()); console.log(`final balance: ${zkapp.account.balance.get().div(1e9)} MINA`); console.log('try to payout a second time..'); -tx = await Mina.transaction(sender, () => { - zkapp.payout(privilegedKey); +tx = await Mina.transaction(sender, async () => { + await zkapp.payout(privilegedKey); }); try { await tx.prove(); @@ -144,8 +153,8 @@ try { console.log('try to payout to a different account..'); try { - tx = await Mina.transaction(sender, () => { - zkapp.payout(Local.testAccounts[2].privateKey); + tx = await Mina.transaction(sender, async () => { + await zkapp.payout(Local.testAccounts[2].privateKey); }); await tx.prove(); await tx.sign([senderKey]).send(); diff --git a/src/examples/simple-zkapp.web.ts b/src/examples/simple-zkapp.web.ts new file mode 100644 index 0000000000..2007df4d90 --- /dev/null +++ b/src/examples/simple-zkapp.web.ts @@ -0,0 +1,130 @@ +import { + Field, + state, + State, + method, + UInt64, + PrivateKey, + SmartContract, + Mina, + AccountUpdate, + Bool, + PublicKey, +} from 'o1js'; + +const doProofs = true; + +const beforeGenesis = UInt64.from(Date.now()); + +class SimpleZkapp extends SmartContract { + @state(Field) x = State(); + + events = { update: Field, payout: UInt64, payoutReceiver: PublicKey }; + + @method async init() { + super.init(); + this.x.set(initialState); + } + + @method.returns(Field) async update(y: Field) { + this.account.provedState.requireEquals(Bool(true)); + this.network.timestamp.requireBetween(beforeGenesis, UInt64.MAXINT()); + this.emitEvent('update', y); + let x = this.x.get(); + this.x.requireEquals(x); + let newX = x.add(y); + this.x.set(newX); + return newX; + } + + /** + * This method allows a certain privileged account to claim half of the zkapp balance, but only once + * @param caller the privileged account + */ + @method async payout(caller: PrivateKey) { + this.account.provedState.requireEquals(Bool(true)); + + // check that caller is the privileged account + let callerAddress = caller.toPublicKey(); + callerAddress.assertEquals(privilegedAddress); + + // assert that the caller account is new - this way, payout can only happen once + let callerAccountUpdate = AccountUpdate.create(callerAddress); + callerAccountUpdate.account.isNew.requireEquals(Bool(true)); + // pay out half of the zkapp balance to the caller + let balance = this.account.balance.get(); + this.account.balance.requireEquals(balance); + let halfBalance = balance.div(2); + this.send({ to: callerAccountUpdate, amount: halfBalance }); + + // emit some events + this.emitEvent('payoutReceiver', callerAddress); + this.emitEvent('payout', halfBalance); + } +} + +let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); +Mina.setActiveInstance(Local); + +// a test account that pays all the fees, and puts additional funds into the zkapp +let { privateKey: senderKey, publicKey: sender } = Local.testAccounts[0]; + +// the zkapp account +let zkappKey = PrivateKey.random(); +let zkappAddress = zkappKey.toPublicKey(); + +// a special account that is allowed to pull out half of the zkapp balance, once +let privilegedKey = PrivateKey.fromBase58( + 'EKEeoESE2A41YQnSht9f7mjiKpJSeZ4jnfHXYatYi8xJdYSxWBep' +); +let privilegedAddress = privilegedKey.toPublicKey(); + +let initialBalance = 10_000_000_000; +let initialState = Field(1); +let zkapp = new SimpleZkapp(zkappAddress); + +if (doProofs) { + console.log('compile'); + await SimpleZkapp.compile(); +} + +console.log('deploy'); +let tx = await Mina.transaction(sender, async () => { + let senderUpdate = AccountUpdate.fundNewAccount(sender); + senderUpdate.send({ to: zkappAddress, amount: initialBalance }); + await zkapp.deploy(); +}); +await tx.prove(); +await tx.sign([senderKey, zkappKey]).send(); + +console.log('initial state: ' + zkapp.x.get()); +console.log(`initial balance: ${zkapp.account.balance.get().div(1e9)} MINA`); + +let account = Mina.getAccount(zkappAddress); +console.log('account state is proved:', account.zkapp?.provedState.toBoolean()); + +console.log('update'); +tx = await Mina.transaction(sender, async () => { + await zkapp.update(Field(3)); +}); +await tx.prove(); +await tx.sign([senderKey]).send(); + +// pay more into the zkapp -- this doesn't need a proof +console.log('receive'); +tx = await Mina.transaction(sender, async () => { + let payerAccountUpdate = AccountUpdate.createSigned(sender); + payerAccountUpdate.send({ to: zkappAddress, amount: UInt64.from(8e9) }); +}); +await tx.sign([senderKey]).send(); + +console.log('payout'); +tx = await Mina.transaction(sender, async () => { + AccountUpdate.fundNewAccount(sender); + await zkapp.payout(privilegedKey); +}); +await tx.prove(); +await tx.sign([senderKey]).send(); + +console.log('final state: ' + zkapp.x.get()); +console.log(`final balance: ${zkapp.account.balance.get().div(1e9)} MINA`); diff --git a/src/examples/tsconfig.json b/src/examples/tsconfig.json index fdcb05727d..56ddb48265 100644 --- a/src/examples/tsconfig.json +++ b/src/examples/tsconfig.json @@ -6,7 +6,7 @@ "rootDir": ".", "baseUrl": "../..", "paths": { - "snarkyjs": ["."] + "o1js": ["."] } } } diff --git a/src/examples/utils/README.md b/src/examples/utils/README.md new file mode 100644 index 0000000000..5a990bb239 --- /dev/null +++ b/src/examples/utils/README.md @@ -0,0 +1 @@ +This folder doesn't contain stand-alone examples, but utilities used in our examples. diff --git a/src/examples/profiler.ts b/src/examples/utils/profiler.ts similarity index 97% rename from src/examples/profiler.ts rename to src/examples/utils/profiler.ts index 9a93e417ee..c7f1e6c5a5 100644 --- a/src/examples/profiler.ts +++ b/src/examples/utils/profiler.ts @@ -1,4 +1,3 @@ -import { time } from 'console'; import fs from 'fs'; export { getProfiler }; diff --git a/src/examples/utils/tic-toc.node.ts b/src/examples/utils/tic-toc.node.ts new file mode 100644 index 0000000000..ea6c4d012d --- /dev/null +++ b/src/examples/utils/tic-toc.node.ts @@ -0,0 +1,21 @@ +/** + * Helper for printing timings, in the spirit of Python's `tic` and `toc`. + * + * This is a slightly nicer version of './tic-toc.ts' which only works in Node. + */ + +export { tic, toc }; + +let timingStack: [string | undefined, number][] = []; + +function tic(label?: string) { + if (label) process.stdout.write(`${label}... `); + timingStack.push([label, performance.now()]); +} + +function toc() { + let [label, start] = timingStack.pop()!; + let time = (performance.now() - start) / 1000; + if (label) process.stdout.write(`\r${label}... ${time.toFixed(3)} sec\n`); + return time; +} diff --git a/src/examples/utils/tic-toc.ts b/src/examples/utils/tic-toc.ts new file mode 100644 index 0000000000..a7d2ab20b8 --- /dev/null +++ b/src/examples/utils/tic-toc.ts @@ -0,0 +1,19 @@ +/** + * Helper for printing timings, in the spirit of Python's `tic` and `toc`. + */ + +export { tic, toc }; + +let timingStack: [string | undefined, number][] = []; + +function tic(label?: string) { + if (label) console.log(`${label}... `); + timingStack.push([label, performance.now()]); +} + +function toc() { + let [label, start] = timingStack.pop()!; + let time = (performance.now() - start) / 1000; + if (label) console.log(`${label}... ${time.toFixed(3)} sec`); + return time; +} diff --git a/src/examples/zkapps/composability.ts b/src/examples/zkapps/composability.ts index 8e1514fa7d..81867b8e6d 100644 --- a/src/examples/zkapps/composability.ts +++ b/src/examples/zkapps/composability.ts @@ -3,7 +3,6 @@ */ import { Field, - isReady, method, Mina, AccountUpdate, @@ -11,16 +10,15 @@ import { SmartContract, state, State, -} from 'snarkyjs'; -import { getProfiler } from '../profiler.js'; +} from 'o1js'; +import { getProfiler } from '../utils/profiler.js'; const doProofs = true; -await isReady; - // contract which can add 1 to a number class Incrementer extends SmartContract { - @method increment(x: Field): Field { + @method.returns(Field) + async increment(x: Field) { return x.add(1); } } @@ -28,12 +26,13 @@ class Incrementer extends SmartContract { // contract which can add two numbers, plus 1, and return the result // incrementing by one is outsourced to another contract (it's cleaner that way, we want to stick to the single responsibility principle) class Adder extends SmartContract { - @method addPlus1(x: Field, y: Field): Field { + @method.returns(Field) + async addPlus1(x: Field, y: Field) { // compute result let sum = x.add(y); // call the other contract to increment let incrementer = new Incrementer(incrementerAddress); - return incrementer.increment(sum); + return await incrementer.increment(sum); } } @@ -42,9 +41,10 @@ class Caller extends SmartContract { @state(Field) sum = State(); events = { sum: Field }; - @method callAddAndEmit(x: Field, y: Field) { + @method + async callAddAndEmit(x: Field, y: Field) { let adder = new Adder(adderAddress); - let sum = adder.addPlus1(x, y); + let sum = await adder.addPlus1(x, y); this.emitEvent('sum', sum); this.sum.set(sum); } @@ -85,19 +85,18 @@ if (doProofs) { } console.log('deploy'); -let tx = await Mina.transaction(feePayer, () => { - // TODO: enable funding multiple accounts properly +let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer, 3); - zkapp.deploy(); - adderZkapp.deploy(); - incrementerZkapp.deploy(); + await zkapp.deploy(); + await adderZkapp.deploy(); + await incrementerZkapp.deploy(); }); await tx.sign([feePayerKey, zkappKey, adderKey, incrementerKey]).send(); console.log('call interaction'); -tx = await Mina.transaction(feePayer, () => { +tx = await Mina.transaction(feePayer, async () => { // we just call one contract here, nothing special to do - zkapp.callAddAndEmit(Field(5), Field(6)); + await zkapp.callAddAndEmit(Field(5), Field(6)); }); console.log('proving (3 proofs.. can take a bit!)'); await tx.prove(); diff --git a/src/examples/zkapps/dex/arbitrary_token_interaction.ts b/src/examples/zkapps/dex/arbitrary_token_interaction.ts deleted file mode 100644 index f4d92a17bd..0000000000 --- a/src/examples/zkapps/dex/arbitrary_token_interaction.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - isReady, - Mina, - AccountUpdate, - UInt64, - shutdown, - TokenId, -} from 'snarkyjs'; -import { TokenContract, addresses, keys, tokenIds } from './dex.js'; - -await isReady; -let doProofs = true; - -let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); -Mina.setActiveInstance(Local); -let accountFee = Mina.accountCreationFee(); - -let [{ privateKey: userKey, publicKey: userAddress }] = Local.testAccounts; -let tx; - -console.log('-------------------------------------------------'); -console.log('TOKEN X ADDRESS\t', addresses.tokenX.toBase58()); -console.log('USER ADDRESS\t', userAddress.toBase58()); -console.log('-------------------------------------------------'); -console.log('TOKEN X ID\t', TokenId.toBase58(tokenIds.X)); -console.log('-------------------------------------------------'); - -// compile & deploy all 5 zkApps -console.log('compile (token)...'); -await TokenContract.compile(); - -let tokenX = new TokenContract(addresses.tokenX); - -console.log('deploy & init token contracts...'); -tx = await Mina.transaction(userKey, () => { - // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves - let feePayerUpdate = AccountUpdate.createSigned(userKey); - feePayerUpdate.balance.subInPlace(accountFee.mul(1)); - tokenX.deploy(); -}); -await tx.prove(); -tx.sign([keys.tokenX]); -await tx.send(); - -console.log('arbitrary token minting...'); -tx = await Mina.transaction(userKey, () => { - // pay fees for creating user's token X account - AccountUpdate.createSigned(userKey).balance.subInPlace(accountFee.mul(1)); - // 😈😈😈 mint any number of tokens to our account 😈😈😈 - let tokenContract = new TokenContract(addresses.tokenX); - tokenContract.token.mint({ - address: userAddress, - amount: UInt64.from(1e18), - }); -}); -await tx.prove(); -console.log(tx.toPretty()); -await tx.send(); - -console.log( - 'User tokens: ', - Mina.getBalance(userAddress, tokenIds.X).value.toBigInt() -); - -shutdown(); diff --git a/src/examples/zkapps/dex/dex-with-actions.ts b/src/examples/zkapps/dex/dex-with-actions.ts index 7557ac7575..a242d7e5d8 100644 --- a/src/examples/zkapps/dex/dex-with-actions.ts +++ b/src/examples/zkapps/dex/dex-with-actions.ts @@ -5,35 +5,43 @@ */ import { Account, - method, AccountUpdate, + AccountUpdateForest, + Field, + InferProvable, + Mina, + Permissions, + Provable, PublicKey, + Reducer, SmartContract, - UInt64, - Struct, State, - state, + Struct, + TokenContract, TokenId, - Reducer, - Field, - Permissions, - isReady, - Mina, - InferProvable, - Provable, -} from 'snarkyjs'; + UInt64, + method, + state, +} from 'o1js'; -import { TokenContract, randomAccounts } from './dex.js'; +import { randomAccounts } from './dex.js'; +import { TrivialCoin } from './erc20.js'; -export { Dex, DexTokenHolder, addresses, keys, tokenIds, getTokenBalances }; +export { Dex, DexTokenHolder, addresses, getTokenBalances, keys, tokenIds }; class RedeemAction extends Struct({ address: PublicKey, dl: UInt64 }) {} -class Dex extends SmartContract { +class Dex extends TokenContract { // addresses of token contracts are constants tokenX = addresses.tokenX; tokenY = addresses.tokenY; + // Approvable API + + @method async approveBase(forest: AccountUpdateForest) { + this.checkZeroBalanceChange(forest); + } + /** * state that keeps track of total lqXY supply -- this is needed to calculate what to return when redeeming liquidity * @@ -70,8 +78,13 @@ class Dex extends SmartContract { }); } - @method createAccount() { - this.token.mint({ address: this.sender, amount: UInt64.from(0) }); + // TODO this could just use `this.approveAccountUpdate()` instead of a separate @method + @method async createAccount() { + this.internal.mint({ + // unconstrained because we don't care which account is created + address: this.sender.getUnconstrained(), + amount: UInt64.from(0), + }); } /** @@ -84,17 +97,19 @@ class Dex extends SmartContract { * This can also be used if the pool is empty. In that case, there is no check on X/Y; * instead, the input X and Y amounts determine the initial ratio. */ - @method supplyLiquidityBase(dx: UInt64, dy: UInt64): UInt64 { - let user = this.sender; - let tokenX = new TokenContract(this.tokenX); - let tokenY = new TokenContract(this.tokenY); + @method.returns(UInt64) + async supplyLiquidityBase(dx: UInt64, dy: UInt64) { + // unconstrained because `transfer()` requires sender signature anyway + let user = this.sender.getUnconstrained(); + let tokenX = new TrivialCoin(this.tokenX); + let tokenY = new TrivialCoin(this.tokenY); // get balances of X and Y token - let dexX = AccountUpdate.create(this.address, tokenX.token.id); - let x = dexX.account.balance.getAndAssertEquals(); + let dexX = AccountUpdate.create(this.address, tokenX.deriveTokenId()); + let x = dexX.account.balance.getAndRequireEquals(); - let dexY = AccountUpdate.create(this.address, tokenY.token.id); - let y = dexY.account.balance.getAndAssertEquals(); + let dexY = AccountUpdate.create(this.address, tokenY.deriveTokenId()); + let y = dexY.account.balance.getAndRequireEquals(); // // assert dy === [dx * y/x], or x === 0 let isXZero = x.equals(UInt64.zero); @@ -102,17 +117,17 @@ class Dex extends SmartContract { let isDyCorrect = dy.equals(dx.mul(y).div(xSafe)); isDyCorrect.or(isXZero).assertTrue(); - tokenX.transfer(user, dexX, dx); - tokenY.transfer(user, dexY, dy); + await tokenX.transfer(user, dexX, dx); + await tokenY.transfer(user, dexY, dy); // calculate liquidity token output simply as dl = dx + dx // => maintains ratio x/l, y/l let dl = dy.add(dx); - this.token.mint({ address: user, amount: dl }); + this.internal.mint({ address: user, amount: dl }); // update l supply let l = this.totalSupply.get(); - this.totalSupply.assertEquals(l); + this.totalSupply.requireEquals(l); this.totalSupply.set(l.add(dl)); // emit event @@ -129,17 +144,17 @@ class Dex extends SmartContract { * the input amount of Y tokens is calculated automatically from the X tokens. * Fails if the liquidity pool is empty, so can't be used for the first deposit. */ - supplyLiquidity(dx: UInt64): UInt64 { + async supplyLiquidity(dx: UInt64) { // calculate dy outside circuit let x = Account(this.address, TokenId.derive(this.tokenX)).balance.get(); let y = Account(this.address, TokenId.derive(this.tokenY)).balance.get(); - if (x.value.isConstant() && x.value.isZero().toBoolean()) { + if (x.value.isConstant() && x.value.equals(0).toBoolean()) { throw Error( 'Cannot call `supplyLiquidity` when reserves are zero. Use `supplyLiquidityBase`.' ); } let dy = dx.mul(y).div(x); - return this.supplyLiquidityBase(dx, dy); + return await this.supplyLiquidityBase(dx, dy); } /** @@ -155,24 +170,28 @@ class Dex extends SmartContract { * @emits RedeemAction - action on the Dex account that will make the token holder * contracts pay you tokens when reducing the action. */ - @method redeemInitialize(dl: UInt64) { - this.reducer.dispatch(new RedeemAction({ address: this.sender, dl })); - this.token.burn({ address: this.sender, amount: dl }); + @method async redeemInitialize(dl: UInt64) { + let sender = this.sender.getUnconstrained(); // unconstrained because `burn()` requires sender signature anyway + this.reducer.dispatch(new RedeemAction({ address: sender, dl })); + this.internal.burn({ address: sender, amount: dl }); // TODO: preconditioning on the state here ruins concurrent interactions, // there should be another `finalize` DEX method which reduces actions & updates state - this.totalSupply.set(this.totalSupply.getAndAssertEquals().sub(dl)); + this.totalSupply.set(this.totalSupply.getAndRequireEquals().sub(dl)); // emit event - this.typedEvents.emit('redeem-liquidity', { address: this.sender, dl }); + this.typedEvents.emit('redeem-liquidity', { address: sender, dl }); } /** * Helper for `DexTokenHolder.redeemFinalize()` which adds preconditions on * the current action state and token supply */ - @method assertActionsAndSupply(actionState: Field, totalSupply: UInt64) { - this.account.actionState.assertEquals(actionState); - this.totalSupply.assertEquals(totalSupply); + @method async assertActionsAndSupply( + actionState: Field, + totalSupply: UInt64 + ) { + this.account.actionState.requireEquals(actionState); + this.totalSupply.requireEquals(totalSupply); } /** @@ -185,11 +204,12 @@ class Dex extends SmartContract { * Note: this is not a `@method`, since it doesn't do anything beyond * the called methods which requires proof authorization. */ - swapX(dx: UInt64): UInt64 { - let tokenY = new TokenContract(this.tokenY); - let dexY = new DexTokenHolder(this.address, tokenY.token.id); - let dy = dexY.swap(this.sender, dx, this.tokenX); - tokenY.approveUpdateAndSend(dexY.self, this.sender, dy); + async swapX(dx: UInt64) { + let user = this.sender.getUnconstrained(); // unconstrained because `swap()` requires sender signature anyway + let tokenY = new TrivialCoin(this.tokenY); + let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId()); + let dy = await dexY.swap(user, dx, this.tokenX); + await tokenY.transfer(dexY.self, user, dy); return dy; } @@ -203,17 +223,14 @@ class Dex extends SmartContract { * Note: this is not a `@method`, since it doesn't do anything beyond * the called methods which requires proof authorization. */ - swapY(dy: UInt64): UInt64 { - let tokenX = new TokenContract(this.tokenX); - let dexX = new DexTokenHolder(this.address, tokenX.token.id); - let dx = dexX.swap(this.sender, dy, this.tokenY); - tokenX.approveUpdateAndSend(dexX.self, this.sender, dx); + async swapY(dy: UInt64) { + let user = this.sender.getUnconstrained(); // unconstrained because `swap()` requires sender signature anyway + let tokenX = new TrivialCoin(this.tokenX); + let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId()); + let dx = await dexX.swap(user, dy, this.tokenY); + await tokenX.transfer(dexX.self, user, dx); return dx; } - - @method transfer(from: PublicKey, to: PublicKey, amount: UInt64) { - this.token.send({ from, to, amount }); - } } class DexTokenHolder extends SmartContract { @@ -233,10 +250,10 @@ class DexTokenHolder extends SmartContract { this.redeemActionState.set(Reducer.initialActionState); } - @method redeemLiquidityFinalize() { + @method async redeemLiquidityFinalize() { // get redeem actions let dex = new Dex(this.address); - let fromActionState = this.redeemActionState.getAndAssertEquals(); + let fromActionState = this.redeemActionState.getAndRequireEquals(); let actions = dex.reducer.getActions({ fromActionState }); // get total supply of liquidity tokens _before_ applying these actions @@ -251,7 +268,7 @@ class DexTokenHolder extends SmartContract { }); // get our token balance - let x = this.account.balance.getAndAssertEquals(); + let x = this.account.balance.getAndRequireEquals(); let redeemActionState = dex.reducer.forEach( actions, @@ -281,26 +298,27 @@ class DexTokenHolder extends SmartContract { this.redeemActionState.set(redeemActionState); // precondition on the DEX contract, to prove we used the right actions & token supply - dex.assertActionsAndSupply(redeemActionState, l); + await dex.assertActionsAndSupply(redeemActionState, l); } // this works for both directions (in our case where both tokens use the same contract) - @method swap( + @method.returns(UInt64) + async swap( user: PublicKey, otherTokenAmount: UInt64, otherTokenAddress: PublicKey - ): UInt64 { + ) { // we're writing this as if our token === y and other token === x let dx = otherTokenAmount; - let tokenX = new TokenContract(otherTokenAddress); + let tokenX = new TrivialCoin(otherTokenAddress); // get balances of X and Y token - let dexX = AccountUpdate.create(this.address, tokenX.token.id); - let x = dexX.account.balance.getAndAssertEquals(); - let y = this.account.balance.getAndAssertEquals(); + let dexX = AccountUpdate.create(this.address, tokenX.deriveTokenId()); + let x = dexX.account.balance.getAndRequireEquals(); + let y = this.account.balance.getAndRequireEquals(); // send x from user to us (i.e., to the same address as this but with the other token) - tokenX.transfer(user, dexX, dx); + await tokenX.transfer(user, dexX, dx); // compute and send dy let dy = y.mul(dx).div(x.add(dx)); @@ -308,15 +326,14 @@ class DexTokenHolder extends SmartContract { this.balance.subInPlace(dy); // emit event - this.typedEvents.emit('swap', { address: this.sender, dx }); + this.typedEvents.emit('swap', { address: user, dx }); return dy; } } -await isReady; let { keys, addresses } = randomAccounts( - false, + process.env.USE_CUSTOM_LOCAL_NETWORK === 'true', 'tokenX', 'tokenY', 'dex', diff --git a/src/examples/zkapps/dex/dex.ts b/src/examples/zkapps/dex/dex.ts index 562adfdb74..385c2a54f8 100644 --- a/src/examples/zkapps/dex/dex.ts +++ b/src/examples/zkapps/dex/dex.ts @@ -1,40 +1,42 @@ import { Account, + AccountUpdate, Bool, - Circuit, - DeployArgs, - Field, - Int64, - isReady, - method, Mina, - AccountUpdate, - Permissions, PrivateKey, + Provable, PublicKey, SmartContract, - UInt64, - VerificationKey, - Struct, State, - state, - UInt32, + Struct, TokenId, - Provable, -} from 'snarkyjs'; + UInt32, + UInt64, + method, + state, + TokenContract as BaseTokenContract, + AccountUpdateForest, +} from 'o1js'; -export { createDex, TokenContract, keys, addresses, tokenIds, randomAccounts }; +export { TokenContract, addresses, createDex, keys, randomAccounts, tokenIds }; class UInt64x2 extends Struct([UInt64, UInt64]) {} function createDex({ lockedLiquiditySlots, }: { lockedLiquiditySlots?: number } = {}) { - class Dex extends SmartContract { + class Dex extends BaseTokenContract { // addresses of token contracts are constants tokenX = addresses.tokenX; tokenY = addresses.tokenY; + // Approvable API + + @method + async approveBase(forest: AccountUpdateForest) { + this.checkZeroBalanceChange(forest); + } + /** * state which keeps track of total lqXY supply -- this is needed to calculate what to return when redeeming liquidity * @@ -52,33 +54,38 @@ function createDex({ * This can also be used if the pool is empty. In that case, there is no check on X/Y; * instead, the input X and Y amounts determine the initial ratio. */ - @method supplyLiquidityBase(dx: UInt64, dy: UInt64): UInt64 { - let user = this.sender; + @method.returns(UInt64) + async supplyLiquidityBase(dx: UInt64, dy: UInt64) { + let user = this.sender.getUnconstrained(); // unconstrained because transfer() requires the signature anyway let tokenX = new TokenContract(this.tokenX); let tokenY = new TokenContract(this.tokenY); // get balances of X and Y token - // TODO: this creates extra account updates. we need to reuse these by passing them to or returning them from transfer() - // but for that, we need the @method argument generalization - let dexXUpdate = AccountUpdate.create(this.address, tokenX.token.id); - let dexXBalance = dexXUpdate.account.balance.getAndAssertEquals(); - - let dexYUpdate = AccountUpdate.create(this.address, tokenY.token.id); - let dexYBalance = dexYUpdate.account.balance.getAndAssertEquals(); - - // // assert dy === [dx * y/x], or x === 0 + let dexXUpdate = AccountUpdate.create( + this.address, + tokenX.deriveTokenId() + ); + let dexXBalance = dexXUpdate.account.balance.getAndRequireEquals(); + + let dexYUpdate = AccountUpdate.create( + this.address, + tokenY.deriveTokenId() + ); + let dexYBalance = dexYUpdate.account.balance.getAndRequireEquals(); + + // assert dy === [dx * y/x], or x === 0 let isXZero = dexXBalance.equals(UInt64.zero); let xSafe = Provable.if(isXZero, UInt64.one, dexXBalance); let isDyCorrect = dy.equals(dx.mul(dexYBalance).div(xSafe)); isDyCorrect.or(isXZero).assertTrue(); - tokenX.transfer(user, dexXUpdate, dx); - tokenY.transfer(user, dexYUpdate, dy); + await tokenX.transfer(user, dexXUpdate, dx); + await tokenY.transfer(user, dexYUpdate, dy); - // calculate liquidity token output simply as dl = dx + dx + // calculate liquidity token output simply as dl = dx + dy // => maintains ratio x/l, y/l let dl = dy.add(dx); - let userUpdate = this.token.mint({ address: user, amount: dl }); + let userUpdate = this.internal.mint({ address: user, amount: dl }); if (lockedLiquiditySlots !== undefined) { /** * exercise the "timing" (vesting) feature to lock the received liquidity tokens. @@ -103,7 +110,7 @@ function createDex({ // update l supply let l = this.totalSupply.get(); - this.totalSupply.assertEquals(l); + this.totalSupply.requireEquals(l); this.totalSupply.set(l.add(dl)); return dl; } @@ -117,17 +124,17 @@ function createDex({ * the input amount of Y tokens is calculated automatically from the X tokens. * Fails if the liquidity pool is empty, so can't be used for the first deposit. */ - supplyLiquidity(dx: UInt64): UInt64 { + async supplyLiquidity(dx: UInt64) { // calculate dy outside circuit let x = Account(this.address, TokenId.derive(this.tokenX)).balance.get(); let y = Account(this.address, TokenId.derive(this.tokenY)).balance.get(); - if (x.value.isConstant() && x.value.isZero().toBoolean()) { + if (x.value.isConstant() && x.value.equals(0).toBoolean()) { throw Error( 'Cannot call `supplyLiquidity` when reserves are zero. Use `supplyLiquidityBase`.' ); } let dy = dx.mul(y).div(x); - return this.supplyLiquidityBase(dx, dy); + return await this.supplyLiquidityBase(dx, dy); } /** @@ -140,13 +147,14 @@ function createDex({ * Note: this is not a `@method` because there's nothing to prove which isn't already proven * by the called methods */ - redeemLiquidity(dl: UInt64) { + async redeemLiquidity(dl: UInt64) { // call the token X holder inside a token X-approved callback + let sender = this.sender.getUnconstrained(); // unconstrained because redeemLiquidity() requires the signature anyway let tokenX = new TokenContract(this.tokenX); - let dexX = new DexTokenHolder(this.address, tokenX.token.id); - let dxdy = dexX.redeemLiquidity(this.sender, dl, this.tokenY); + let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId()); + let dxdy = await dexX.redeemLiquidity(sender, dl, this.tokenY); let dx = dxdy[0]; - tokenX.approveUpdateAndSend(dexX.self, this.sender, dx); + await tokenX.transfer(dexX.self, sender, dx); return dxdy; } @@ -157,11 +165,13 @@ function createDex({ * * The transaction needs to be signed by the user's private key. */ - @method swapX(dx: UInt64): UInt64 { + @method.returns(UInt64) + async swapX(dx: UInt64) { + let sender = this.sender.getUnconstrained(); // unconstrained because swap() requires the signature anyway let tokenY = new TokenContract(this.tokenY); - let dexY = new DexTokenHolder(this.address, tokenY.token.id); - let dy = dexY.swap(this.sender, dx, this.tokenX); - tokenY.approveUpdateAndSend(dexY.self, this.sender, dy); + let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId()); + let dy = await dexY.swap(sender, dx, this.tokenX); + await tokenY.transfer(dexY.self, sender, dy); return dy; } @@ -172,11 +182,13 @@ function createDex({ * * The transaction needs to be signed by the user's private key. */ - @method swapY(dy: UInt64): UInt64 { + @method.returns(UInt64) + async swapY(dy: UInt64) { + let sender = this.sender.getUnconstrained(); // unconstrained because swap() requires the signature anyway let tokenX = new TokenContract(this.tokenX); - let dexX = new DexTokenHolder(this.address, tokenX.token.id); - let dx = dexX.swap(this.sender, dy, this.tokenY); - tokenX.approveUpdateAndSend(dexX.self, this.sender, dx); + let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId()); + let dx = await dexX.swap(sender, dy, this.tokenY); + await tokenX.transfer(dexX.self, sender, dx); return dx; } @@ -191,26 +203,34 @@ function createDex({ * * The transaction needs to be signed by the user's private key. */ - @method burnLiquidity(user: PublicKey, dl: UInt64): UInt64 { + @method.returns(UInt64) + async burnLiquidity(user: PublicKey, dl: UInt64) { // this makes sure there is enough l to burn (user balance stays >= 0), so l stays >= 0, so l was >0 before - this.token.burn({ address: user, amount: dl }); + this.internal.burn({ address: user, amount: dl }); let l = this.totalSupply.get(); - this.totalSupply.assertEquals(l); + this.totalSupply.requireEquals(l); this.totalSupply.set(l.sub(dl)); return l; } - - @method transfer(from: PublicKey, to: PublicKey, amount: UInt64) { - this.token.send({ from, to, amount }); - } } class ModifiedDex extends Dex { - @method swapX(dx: UInt64): UInt64 { + async deploy() { + await super.deploy(); + // override the isNew requirement for re-deploying + this.account.isNew.requireNothing(); + } + + @method.returns(UInt64) + async swapX(dx: UInt64) { + let sender = this.sender.getUnconstrained(); // unconstrained because swap() requires the signature anyway let tokenY = new TokenContract(this.tokenY); - let dexY = new ModifiedDexTokenHolder(this.address, tokenY.token.id); - let dy = dexY.swap(this.sender, dx, this.tokenX); - tokenY.approveUpdateAndSend(dexY.self, this.sender, dy); + let dexY = new ModifiedDexTokenHolder( + this.address, + tokenY.deriveTokenId() + ); + let dy = await dexY.swap(sender, dx, this.tokenX); + await tokenY.transfer(dexY.self, sender, dy); return dy; } } @@ -220,14 +240,15 @@ function createDex({ // it's incomplete, as it gives the user only the Y part for an lqXY token; but doesn't matter as there's no incentive to call it directly // see the more complicated method `redeemLiquidity` below which gives back both tokens, by calling this method, // for the other token, in a callback - @method redeemLiquidityPartial(user: PublicKey, dl: UInt64): UInt64x2 { + @method.returns(UInt64x2) + async redeemLiquidityPartial(user: PublicKey, dl: UInt64) { // user burns dl, approved by the Dex main contract let dex = new Dex(addresses.dex); - let l = dex.burnLiquidity(user, dl); + let l = await dex.burnLiquidity(user, dl); // in return, we give dy back let y = this.account.balance.get(); - this.account.balance.assertEquals(y); + this.account.balance.requireEquals(y); // we can safely divide by l here because the Dex contract logic wouldn't allow burnLiquidity if not l>0 let dy = y.mul(dl).div(l); // just subtract the balance, user gets their part one level higher @@ -241,22 +262,23 @@ function createDex({ } // more complicated circuit, where we trigger the Y(other)-lqXY trade in our child account updates and then add the X(our) part - @method redeemLiquidity( + @method.returns(UInt64x2) + async redeemLiquidity( user: PublicKey, dl: UInt64, otherTokenAddress: PublicKey - ): UInt64x2 { + ) { // first call the Y token holder, approved by the Y token contract; this makes sure we get dl, the user's lqXY let tokenY = new TokenContract(otherTokenAddress); - let dexY = new DexTokenHolder(this.address, tokenY.token.id); - let result = dexY.redeemLiquidityPartial(user, dl); + let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId()); + let result = await dexY.redeemLiquidityPartial(user, dl); let l = result[0]; let dy = result[1]; - tokenY.approveUpdateAndSend(dexY.self, user, dy); + await tokenY.transfer(dexY.self, user, dy); // in return for dl, we give back dx, the X token part let x = this.account.balance.get(); - this.account.balance.assertEquals(x); + this.account.balance.requireEquals(x); let dx = x.mul(dl).div(l); // just subtract the balance, user gets their part one level higher this.balance.subInPlace(dx); @@ -265,20 +287,21 @@ function createDex({ } // this works for both directions (in our case where both tokens use the same contract) - @method swap( + @method.returns(UInt64) + async swap( user: PublicKey, otherTokenAmount: UInt64, otherTokenAddress: PublicKey - ): UInt64 { + ) { // we're writing this as if our token === y and other token === x let dx = otherTokenAmount; let tokenX = new TokenContract(otherTokenAddress); // get balances - let x = tokenX.getBalance(this.address); - let y = this.account.balance.get(); - this.account.balance.assertEquals(y); + let dexX = AccountUpdate.create(this.address, tokenX.deriveTokenId()); + let x = dexX.account.balance.getAndRequireEquals(); + let y = this.account.balance.getAndRequireEquals(); // send x from user to us (i.e., to the same address as this but with the other token) - tokenX.transfer(user, this.address, dx); + await tokenX.transfer(user, dexX, dx); // compute and send dy let dy = y.mul(dx).div(x.add(dx)); // just subtract dy balance and let adding balance be handled one level higher @@ -291,17 +314,20 @@ function createDex({ /** * This swap method has a slightly changed formula */ - @method swap( + @method.returns(UInt64) + async swap( user: PublicKey, otherTokenAmount: UInt64, otherTokenAddress: PublicKey - ): UInt64 { + ) { let dx = otherTokenAmount; let tokenX = new TokenContract(otherTokenAddress); - let x = tokenX.getBalance(this.address); + // get balances + let dexX = AccountUpdate.create(this.address, tokenX.deriveTokenId()); + let x = dexX.account.balance.getAndRequireEquals(); let y = this.account.balance.get(); - this.account.balance.assertEquals(y); - tokenX.transfer(user, this.address, dx); + this.account.balance.requireEquals(y); + await tokenX.transfer(user, dexX, dx); // this formula has been changed - we just give the user an additional 15 token let dy = y.mul(dx).div(x.add(dx)).add(15); @@ -373,15 +399,8 @@ function createDex({ /** * Simple token with API flexible enough to handle all our use cases */ -class TokenContract extends SmartContract { - deploy(args?: DeployArgs) { - super.deploy(args); - this.account.permissions.set({ - ...Permissions.default(), - access: Permissions.proofOrSignature(), - }); - } - @method init() { +class TokenContract extends BaseTokenContract { + @method async init() { super.init(); // mint the entire supply to the token account with the same address as this contract /** @@ -389,14 +408,14 @@ class TokenContract extends SmartContract { * * we mint the max uint64 of tokens here, so that we can overflow it in tests if we just mint a bit more */ - let receiver = this.token.mint({ + let receiver = this.internal.mint({ address: this.address, amount: UInt64.MAXINT(), }); // assert that the receiving account is new, so this can be only done once - receiver.account.isNew.assertEquals(Bool(true)); + receiver.account.isNew.requireEquals(Bool(true)); // pay fees for opened account - this.balance.subInPlace(Mina.accountCreationFee()); + this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); } /** @@ -404,83 +423,20 @@ class TokenContract extends SmartContract { * * mint additional tokens to some user, so we can overflow token balances */ - @method init2() { - let receiver = this.token.mint({ + @method async init2() { + let receiver = this.internal.mint({ address: addresses.user, amount: UInt64.from(10n ** 6n), }); // assert that the receiving account is new, so this can be only done once - receiver.account.isNew.assertEquals(Bool(true)); + receiver.account.isNew.requireEquals(Bool(true)); // pay fees for opened account - this.balance.subInPlace(Mina.accountCreationFee()); - } - - // this is a very standardized deploy method. instead, we could also take the account update from a callback - // => need callbacks for signatures - @method deployZkapp(address: PublicKey, verificationKey: VerificationKey) { - let tokenId = this.token.id; - let zkapp = AccountUpdate.create(address, tokenId); - zkapp.account.permissions.set(Permissions.default()); - zkapp.account.verificationKey.set(verificationKey); - zkapp.requireSignature(); - } - - @method approveUpdate(zkappUpdate: AccountUpdate) { - this.approve(zkappUpdate); - let balanceChange = Int64.fromObject(zkappUpdate.body.balanceChange); - balanceChange.assertEquals(Int64.from(0)); - } - - // FIXME: remove this - @method approveAny(zkappUpdate: AccountUpdate) { - this.approve(zkappUpdate, AccountUpdate.Layout.AnyChildren); + this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); } - // let a zkapp send tokens to someone, provided the token supply stays constant - @method approveUpdateAndSend( - zkappUpdate: AccountUpdate, - to: PublicKey, - amount: UInt64 - ) { - // TODO: THIS IS INSECURE. The proper version has a prover error (compile != prove) that must be fixed - this.approve(zkappUpdate, AccountUpdate.Layout.AnyChildren); - - // THIS IS HOW IT SHOULD BE DONE: - // // approve a layout of two grandchildren, both of which can't inherit the token permission - // let { StaticChildren, AnyChildren } = AccountUpdate.Layout; - // this.approve(zkappUpdate, StaticChildren(AnyChildren, AnyChildren)); - // zkappUpdate.body.mayUseToken.parentsOwnToken.assertTrue(); - // let [grandchild1, grandchild2] = zkappUpdate.children.accountUpdates; - // grandchild1.body.mayUseToken.inheritFromParent.assertFalse(); - // grandchild2.body.mayUseToken.inheritFromParent.assertFalse(); - - // see if balance change cancels the amount sent - let balanceChange = Int64.fromObject(zkappUpdate.body.balanceChange); - balanceChange.assertEquals(Int64.from(amount).neg()); - // add same amount of tokens to the receiving address - this.token.mint({ address: to, amount }); - } - - transfer(from: PublicKey, to: PublicKey | AccountUpdate, amount: UInt64) { - if (to instanceof PublicKey) - return this.transferToAddress(from, to, amount); - if (to instanceof AccountUpdate) - return this.transferToUpdate(from, to, amount); - } - @method transferToAddress(from: PublicKey, to: PublicKey, value: UInt64) { - this.token.send({ from, to, amount: value }); - } - @method transferToUpdate(from: PublicKey, to: AccountUpdate, value: UInt64) { - this.token.send({ from, to, amount: value }); - } - - @method getBalance(publicKey: PublicKey): UInt64 { - let accountUpdate = AccountUpdate.create(publicKey, this.token.id); - let balance = accountUpdate.account.balance.get(); - accountUpdate.account.balance.assertEquals( - accountUpdate.account.balance.get() - ); - return balance; + @method + async approveBase(forest: AccountUpdateForest) { + this.checkZeroBalanceChange(forest); } } @@ -493,9 +449,8 @@ const savedKeys = [ 'EKEyPVU37EGw8CdGtUYnfDcBT2Eu7B6rSdy64R68UHYbrYbVJett', ]; -await isReady; let { keys, addresses } = randomAccounts( - false, + process.env.USE_CUSTOM_LOCAL_NETWORK === 'true', 'tokenX', 'tokenY', 'dex', @@ -509,19 +464,6 @@ let tokenIds = { lqXY: TokenId.derive(addresses.dex), }; -/** - * Sum of balances of the account update and all its descendants - */ -function balanceSum(accountUpdate: AccountUpdate, tokenId: Field) { - let myTokenId = accountUpdate.body.tokenId; - let myBalance = Int64.fromObject(accountUpdate.body.balanceChange); - let balance = Provable.if(myTokenId.equals(tokenId), myBalance, Int64.zero); - for (let child of accountUpdate.children.accountUpdates) { - balance = balance.add(balanceSum(child, tokenId)); - } - return balance; -} - /** * Predefined accounts keys, labeled by the input strings. Useful for testing/debugging with consistent keys. */ diff --git a/src/examples/zkapps/dex/erc20.ts b/src/examples/zkapps/dex/erc20.ts index f214e1eb54..586b301faa 100644 --- a/src/examples/zkapps/dex/erc20.ts +++ b/src/examples/zkapps/dex/erc20.ts @@ -2,39 +2,51 @@ import { ProvablePure, Bool, CircuitString, - provablePure, DeployArgs, Field, method, AccountUpdate, PublicKey, - SmartContract, UInt64, - Account, - Experimental, Permissions, Mina, - Int64, - VerificationKey, -} from 'snarkyjs'; + TokenContract, + AccountUpdateForest, + Struct, + TransactionVersion, +} from 'o1js'; + +export { Erc20Like, TrivialCoin }; /** - * ERC-20 token standard. + * ERC-20-like token standard. * https://ethereum.org/en/developers/docs/standards/tokens/erc-20/ + * + * Differences to ERC-20: + * - No approvals / allowance, because zkApps don't need them and they are a security liability. + * - `transfer()` and `transferFrom()` are collapsed into a single `transfer()` method which takes + * both the sender and the receiver as arguments. + * - `transfer()` and `balanceOf()` can also take an account update as an argument. + * This form is needed for zkApp token accounts, where the account update has to come from a method + * (in order to get proof authorization), and can't be created by the token contract itself. + * - `transfer()` doesn't return a boolean, because in the zkApp protocol, + * a transaction succeeds or fails in its entirety, and there is no need to handle partial failures. + * - All method signatures are async to support async circuits / fetching data from the chain. */ -type Erc20 = { +type Erc20Like = { // pure view functions which don't need @method - name?: () => CircuitString; - symbol?: () => CircuitString; - decimals?: () => Field; // TODO: should be UInt8 which doesn't exist yet - totalSupply(): UInt64; - balanceOf(owner: PublicKey): UInt64; - allowance(owner: PublicKey, spender: PublicKey): UInt64; + name?: () => Promise; + symbol?: () => Promise; + decimals?: () => Promise; + totalSupply(): Promise; + balanceOf(owner: PublicKey | AccountUpdate): Promise; // mutations which need @method - transfer(to: PublicKey, value: UInt64): Bool; // emits "Transfer" event - transferFrom(from: PublicKey, to: PublicKey, value: UInt64): Bool; // emits "Transfer" event - approveSpend(spender: PublicKey, value: UInt64): Bool; // emits "Approve" event + transfer( + from: PublicKey | AccountUpdate, + to: PublicKey | AccountUpdate, + value: UInt64 + ): Promise; // emits "Transfer" event // events events: { @@ -43,11 +55,6 @@ type Erc20 = { to: PublicKey; value: UInt64; }>; - Approval: ProvablePure<{ - owner: PublicKey; - spender: PublicKey; - value: UInt64; - }>; }; }; @@ -61,143 +68,76 @@ type Erc20 = { * Functionality: * Just enough to be swapped by the DEX contract, and be secure */ -class TrivialCoin extends SmartContract implements Erc20 { +class TrivialCoin extends TokenContract implements Erc20Like { // constant supply SUPPLY = UInt64.from(10n ** 18n); - deploy(args: DeployArgs) { - super.deploy(args); - this.account.tokenSymbol.set('SOM'); + async deploy(args?: DeployArgs) { + await super.deploy(args); + this.account.tokenSymbol.set('TRIV'); + + // make account non-upgradable forever this.account.permissions.set({ ...Permissions.default(), - setPermissions: Permissions.proof(), + setVerificationKey: { + auth: Permissions.impossible(), + txnVersion: TransactionVersion.current(), + }, + setPermissions: Permissions.impossible(), + access: Permissions.proofOrSignature(), }); } - @method init() { + + @method async init() { super.init(); // mint the entire supply to the token account with the same address as this contract let address = this.self.body.publicKey; - let receiver = this.token.mint({ - address, - amount: this.SUPPLY, - }); + let receiver = this.internal.mint({ address, amount: this.SUPPLY }); + // assert that the receiving account is new, so this can be only done once - receiver.account.isNew.assertEquals(Bool(true)); + receiver.account.isNew.requireEquals(Bool(true)); + // pay fees for opened account - this.balance.subInPlace(Mina.accountCreationFee()); + this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); // since this is the only method of this zkApp that resets the entire state, provedState: true implies // that this function was run. Since it can be run only once, this implies it was run exactly once - - // make account non-upgradable forever - this.account.permissions.set({ - ...Permissions.default(), - setVerificationKey: Permissions.impossible(), - setPermissions: Permissions.impossible(), - access: Permissions.proofOrSignature(), - }); } // ERC20 API - name(): CircuitString { - return CircuitString.fromString('SomeCoin'); + async name() { + return CircuitString.fromString('TrivialCoin'); } - symbol(): CircuitString { - return CircuitString.fromString('SOM'); + async symbol() { + return CircuitString.fromString('TRIV'); } - decimals(): Field { + async decimals() { return Field(9); } - totalSupply(): UInt64 { + async totalSupply() { return this.SUPPLY; } - balanceOf(owner: PublicKey): UInt64 { - let account = Account(owner, this.token.id); - let balance = account.balance.get(); - account.balance.assertEquals(balance); - return balance; - } - allowance(owner: PublicKey, spender: PublicKey): UInt64 { - // TODO: implement allowances - return UInt64.zero; - } - - @method transfer(to: PublicKey, value: UInt64): Bool { - this.token.send({ from: this.sender, to, amount: value }); - this.emitEvent('Transfer', { from: this.sender, to, value }); - // we don't have to check the balance of the sender -- this is done by the zkApp protocol - return Bool(true); - } - @method transferFrom(from: PublicKey, to: PublicKey, value: UInt64): Bool { - this.token.send({ from, to, amount: value }); - this.emitEvent('Transfer', { from, to, value }); - // we don't have to check the balance of the sender -- this is done by the zkApp protocol - return Bool(true); - } - @method approveSpend(spender: PublicKey, value: UInt64): Bool { - // TODO: implement allowances - return Bool(false); + async balanceOf(owner: PublicKey | AccountUpdate) { + let update = + owner instanceof PublicKey + ? AccountUpdate.create(owner, this.deriveTokenId()) + : owner; + await this.approveAccountUpdate(update); + return update.account.balance.getAndRequireEquals(); } events = { - Transfer: provablePure({ - from: PublicKey, - to: PublicKey, - value: UInt64, - }), - Approval: provablePure({ - owner: PublicKey, - spender: PublicKey, - value: UInt64, - }), + Transfer: Struct({ from: PublicKey, to: PublicKey, value: UInt64 }), }; - // additional API needed for zkApp token accounts - - @method transferFromZkapp( - from: PublicKey, - to: PublicKey, - value: UInt64, - approve: Experimental.Callback - ): Bool { - // TODO: need to be able to witness a certain layout of account updates, in this case - // tokenContract --> sender --> receiver - let fromUpdate = this.approve(approve, AccountUpdate.Layout.NoChildren); - - let negativeAmount = Int64.fromObject(fromUpdate.body.balanceChange); - negativeAmount.assertEquals(Int64.from(value).neg()); - let tokenId = this.token.id; - fromUpdate.body.tokenId.assertEquals(tokenId); - fromUpdate.body.publicKey.assertEquals(from); - - let toUpdate = AccountUpdate.create(to, tokenId); - toUpdate.balance.addInPlace(value); - this.emitEvent('Transfer', { from, to, value }); - return Bool(true); - } + // TODO: doesn't emit a Transfer event yet + // need to make transfer() a separate method from approveBase, which does the same as + // `transfer()` on the base contract, but also emits the event - // this is a very standardized deploy method. instead, we could also take the account update from a callback - @method deployZkapp( - zkappAddress: PublicKey, - verificationKey: VerificationKey - ) { - let tokenId = this.token.id; - let zkapp = Experimental.createChildAccountUpdate( - this.self, - zkappAddress, - tokenId - ); - zkapp.account.permissions.set(Permissions.default()); - zkapp.account.verificationKey.set(verificationKey); - zkapp.requireSignature(); - } + // implement Approvable API - // for letting a zkapp do whatever it wants, as long as no tokens are transfered - // TODO: atm, we have to restrict the zkapp to have no children - // -> need to be able to witness a general layout of account updates - @method approveZkapp(callback: Experimental.Callback) { - let zkappUpdate = this.approve(callback, AccountUpdate.Layout.NoChildren); - Int64.fromObject(zkappUpdate.body.balanceChange).assertEquals(UInt64.zero); + @method async approveBase(forest: AccountUpdateForest) { + this.checkZeroBalanceChange(forest); } } diff --git a/src/examples/zkapps/dex/happy-path-with-actions.ts b/src/examples/zkapps/dex/happy-path-with-actions.ts index a8b6f9f23f..7b186e641d 100644 --- a/src/examples/zkapps/dex/happy-path-with-actions.ts +++ b/src/examples/zkapps/dex/happy-path-with-actions.ts @@ -1,29 +1,24 @@ -import { isReady, Mina, AccountUpdate, UInt64 } from 'snarkyjs'; +import { expect } from 'expect'; +import { AccountUpdate, Mina, UInt64 } from 'o1js'; +import { tic, toc } from '../../utils/tic-toc.node.js'; import { Dex, DexTokenHolder, addresses, + getTokenBalances, keys, tokenIds, - getTokenBalances, } from './dex-with-actions.js'; -import { TokenContract } from './dex.js'; -import { expect } from 'expect'; -import { tic, toc } from '../tictoc.js'; - -await isReady; - -let proofsEnabled = false; +import { TrivialCoin as TokenContract } from './erc20.js'; +let proofsEnabled = true; tic('Happy path with actions'); console.log(); - let Local = Mina.LocalBlockchain({ proofsEnabled, enforceTransactionLimits: true, }); Mina.setActiveInstance(Local); -let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances, oldBalances; @@ -47,14 +42,15 @@ let dexTokenHolderX = new DexTokenHolder(addresses.dex, tokenIds.X); let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); tic('deploy & init token contracts'); -tx = await Mina.transaction(feePayerAddress, () => { +tx = await Mina.transaction(feePayerAddress, async () => { + await tokenX.deploy(); + await tokenY.deploy(); + // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves - let feePayerUpdate = AccountUpdate.createSigned(feePayerAddress); - feePayerUpdate.balance.subInPlace(accountFee.mul(2)); - feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee }); - feePayerUpdate.send({ to: addresses.tokenY, amount: accountFee }); - tokenX.deploy(); - tokenY.deploy(); + const accountFee = Mina.getNetworkConstants().accountCreationFee; + let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); + feePayerUpdate.send({ to: tokenX.self, amount: accountFee }); + feePayerUpdate.send({ to: tokenY.self, amount: accountFee }); }); await tx.prove(); await tx.sign([feePayerKey, keys.tokenX, keys.tokenY]).send(); @@ -62,16 +58,16 @@ toc(); console.log('account updates length', tx.transaction.accountUpdates.length); tic('deploy dex contracts'); -tx = await Mina.transaction(feePayerAddress, () => { +tx = await Mina.transaction(feePayerAddress, async () => { // pay fees for creating 3 dex accounts AccountUpdate.createSigned(feePayerAddress).balance.subInPlace( - accountFee.mul(3) + Mina.getNetworkConstants().accountCreationFee.mul(3) ); - dex.deploy(); - dexTokenHolderX.deploy(); - tokenX.approveUpdate(dexTokenHolderX.self); - dexTokenHolderY.deploy(); - tokenY.approveUpdate(dexTokenHolderY.self); + await dex.deploy(); + await dexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); await tx.sign([feePayerKey, keys.dex]).send(); @@ -80,12 +76,12 @@ console.log('account updates length', tx.transaction.accountUpdates.length); tic('transfer tokens to user'); let USER_DX = 1_000n; -tx = await Mina.transaction(feePayerAddress, () => { +tx = await Mina.transaction(feePayerAddress, async () => { // pay fees for creating 3 user accounts let feePayer = AccountUpdate.fundNewAccount(feePayerAddress, 3); feePayer.send({ to: addresses.user, amount: 20e9 }); // give users MINA to pay fees - tokenX.transfer(addresses.tokenX, addresses.user, UInt64.from(USER_DX)); - tokenY.transfer(addresses.tokenY, addresses.user, UInt64.from(USER_DX)); + await tokenX.transfer(addresses.tokenX, addresses.user, UInt64.from(USER_DX)); + await tokenY.transfer(addresses.tokenY, addresses.user, UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([feePayerKey, keys.tokenX, keys.tokenY]).send(); @@ -94,9 +90,9 @@ console.log('account updates length', tx.transaction.accountUpdates.length); // this is done in advance to avoid account update limit in `supply` tic("create user's lq token account"); -tx = await Mina.transaction(addresses.user, () => { +tx = await Mina.transaction(addresses.user, async () => { AccountUpdate.fundNewAccount(addresses.user); - dex.createAccount(); + await dex.createAccount(); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -108,8 +104,8 @@ expect(balances.user.X).toEqual(USER_DX); console.log(balances); tic('supply liquidity'); -tx = await Mina.transaction(addresses.user, () => { - dex.supplyLiquidityBase(UInt64.from(USER_DX), UInt64.from(USER_DX)); +tx = await Mina.transaction(addresses.user, async () => { + await dex.supplyLiquidityBase(UInt64.from(USER_DX), UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -121,8 +117,8 @@ console.log(balances); tic('redeem liquidity, step 1'); let USER_DL = 100n; -tx = await Mina.transaction(addresses.user, () => { - dex.redeemInitialize(UInt64.from(USER_DL)); +tx = await Mina.transaction(addresses.user, async () => { + await dex.redeemInitialize(UInt64.from(USER_DL)); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -131,9 +127,9 @@ console.log('account updates length', tx.transaction.accountUpdates.length); console.log(getTokenBalances()); tic('redeem liquidity, step 2a (get back token X)'); -tx = await Mina.transaction(addresses.user, () => { - dexTokenHolderX.redeemLiquidityFinalize(); - tokenX.approveAny(dexTokenHolderX.self); +tx = await Mina.transaction(addresses.user, async () => { + await dexTokenHolderX.redeemLiquidityFinalize(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -142,9 +138,9 @@ console.log('account updates length', tx.transaction.accountUpdates.length); console.log(getTokenBalances()); tic('redeem liquidity, step 2b (get back token Y)'); -tx = await Mina.transaction(addresses.user, () => { - dexTokenHolderY.redeemLiquidityFinalize(); - tokenY.approveAny(dexTokenHolderY.self); +tx = await Mina.transaction(addresses.user, async () => { + await dexTokenHolderY.redeemLiquidityFinalize(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -157,8 +153,8 @@ expect(balances.user.X).toEqual(USER_DL / 2n); tic('swap 10 X for Y'); USER_DX = 10n; -tx = await Mina.transaction(addresses.user, () => { - dex.swapX(UInt64.from(USER_DX)); +tx = await Mina.transaction(addresses.user, async () => { + await dex.swapX(UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); diff --git a/src/examples/zkapps/dex/happy-path-with-proofs.ts b/src/examples/zkapps/dex/happy-path-with-proofs.ts index e129ce559a..09395d9801 100644 --- a/src/examples/zkapps/dex/happy-path-with-proofs.ts +++ b/src/examples/zkapps/dex/happy-path-with-proofs.ts @@ -1,10 +1,8 @@ -import { isReady, Mina, AccountUpdate, UInt64 } from 'snarkyjs'; -import { createDex, TokenContract, addresses, keys, tokenIds } from './dex.js'; import { expect } from 'expect'; -import { tic, toc } from '../tictoc.js'; -import { getProfiler } from '../../profiler.js'; - -await isReady; +import { AccountUpdate, Mina, UInt64 } from 'o1js'; +import { getProfiler } from '../../utils/profiler.js'; +import { tic, toc } from '../../utils/tic-toc.node.js'; +import { TokenContract, addresses, createDex, keys, tokenIds } from './dex.js'; const TokenProfiler = getProfiler('Token with Proofs'); TokenProfiler.start('Token with proofs test flow'); @@ -12,22 +10,20 @@ let proofsEnabled = true; tic('Happy path with proofs'); console.log(); - let Local = Mina.LocalBlockchain({ proofsEnabled, enforceTransactionLimits: false, }); Mina.setActiveInstance(Local); -let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances, oldBalances; let { Dex, DexTokenHolder, getTokenBalances } = createDex(); -TokenContract.analyzeMethods(); -DexTokenHolder.analyzeMethods(); -Dex.analyzeMethods(); +await TokenContract.analyzeMethods(); +await DexTokenHolder.analyzeMethods(); +await Dex.analyzeMethods(); if (proofsEnabled) { tic('compile (token)'); @@ -48,14 +44,15 @@ let dexTokenHolderX = new DexTokenHolder(addresses.dex, tokenIds.X); let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); tic('deploy & init token contracts'); -tx = await Mina.transaction(feePayerAddress, () => { +tx = await Mina.transaction(feePayerAddress, async () => { + await tokenX.deploy(); + await tokenY.deploy(); + // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves - let feePayerUpdate = AccountUpdate.createSigned(feePayerAddress); - feePayerUpdate.balance.subInPlace(accountFee.mul(2)); - feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee }); - feePayerUpdate.send({ to: addresses.tokenY, amount: accountFee }); - tokenX.deploy(); - tokenY.deploy(); + const accountFee = Mina.getNetworkConstants().accountCreationFee; + let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); + feePayerUpdate.send({ to: tokenX.self, amount: accountFee }); + feePayerUpdate.send({ to: tokenY.self, amount: accountFee }); }); await tx.prove(); await tx.sign([feePayerKey, keys.tokenX, keys.tokenY]).send(); @@ -63,16 +60,16 @@ toc(); console.log('account updates length', tx.transaction.accountUpdates.length); tic('deploy dex contracts'); -tx = await Mina.transaction(feePayerAddress, () => { +tx = await Mina.transaction(feePayerAddress, async () => { // pay fees for creating 3 dex accounts AccountUpdate.createSigned(feePayerAddress).balance.subInPlace( - accountFee.mul(3) + Mina.getNetworkConstants().accountCreationFee.mul(3) ); - dex.deploy(); - dexTokenHolderX.deploy(); - tokenX.approveUpdate(dexTokenHolderX.self); - dexTokenHolderY.deploy(); - tokenY.approveUpdate(dexTokenHolderY.self); + await dex.deploy(); + await dexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); await tx.sign([feePayerKey, keys.dex]).send(); @@ -81,12 +78,12 @@ console.log('account updates length', tx.transaction.accountUpdates.length); tic('transfer tokens to user'); let USER_DX = 1_000n; -tx = await Mina.transaction(feePayerAddress, () => { +tx = await Mina.transaction(feePayerAddress, async () => { // pay fees for creating 3 user accounts let feePayer = AccountUpdate.fundNewAccount(feePayerAddress, 3); feePayer.send({ to: addresses.user, amount: 20e9 }); // give users MINA to pay fees - tokenX.transfer(addresses.tokenX, addresses.user, UInt64.from(USER_DX)); - tokenY.transfer(addresses.tokenY, addresses.user, UInt64.from(USER_DX)); + await tokenX.transfer(addresses.tokenX, addresses.user, UInt64.from(USER_DX)); + await tokenY.transfer(addresses.tokenY, addresses.user, UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([feePayerKey, keys.tokenX, keys.tokenY]).send(); @@ -96,9 +93,9 @@ console.log('account updates length', tx.transaction.accountUpdates.length); expect(balances.user.X).toEqual(USER_DX); tic('supply liquidity'); -tx = await Mina.transaction(addresses.user, () => { +tx = await Mina.transaction(addresses.user, async () => { AccountUpdate.fundNewAccount(addresses.user); - dex.supplyLiquidityBase(UInt64.from(USER_DX), UInt64.from(USER_DX)); + await dex.supplyLiquidityBase(UInt64.from(USER_DX), UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -109,9 +106,10 @@ expect(balances.user.X).toEqual(0n); tic('redeem liquidity'); let USER_DL = 100n; -tx = await Mina.transaction(addresses.user, () => { - dex.redeemLiquidity(UInt64.from(USER_DL)); +tx = await Mina.transaction(addresses.user, async () => { + await dex.redeemLiquidity(UInt64.from(USER_DL)); }); + await tx.prove(); await tx.sign([keys.user]).send(); toc(); @@ -121,8 +119,8 @@ expect(balances.user.X).toEqual(USER_DL / 2n); tic('swap 10 X for Y'); USER_DX = 10n; -tx = await Mina.transaction(addresses.user, () => { - dex.swapX(UInt64.from(USER_DX)); +tx = await Mina.transaction(addresses.user, async () => { + await dex.swapX(UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); diff --git a/src/examples/zkapps/dex/run-berkeley.ts b/src/examples/zkapps/dex/run-live.ts similarity index 67% rename from src/examples/zkapps/dex/run-berkeley.ts rename to src/examples/zkapps/dex/run-live.ts index 3f4f7f9cc6..17a94f28d5 100644 --- a/src/examples/zkapps/dex/run-berkeley.ts +++ b/src/examples/zkapps/dex/run-live.ts @@ -1,11 +1,14 @@ +import { expect } from 'expect'; import { - isReady, - Mina, AccountUpdate, - UInt64, + Lightnet, + Mina, PrivateKey, + UInt64, fetchAccount, -} from 'snarkyjs'; +} from 'o1js'; +import os from 'os'; +import { tic, toc } from '../../utils/tic-toc.node.js'; import { Dex, DexTokenHolder, @@ -13,35 +16,40 @@ import { keys, tokenIds, } from './dex-with-actions.js'; -import { TokenContract } from './dex.js'; -import { expect } from 'expect'; -import { tic, toc } from '../tictoc.js'; - -await isReady; +import { TrivialCoin as TokenContract } from './erc20.js'; +const useCustomLocalNetwork = process.env.USE_CUSTOM_LOCAL_NETWORK === 'true'; // setting this to a higher number allows you to skip a few transactions, to pick up after an error const successfulTransactions = 0; -tic('Run DEX with actions, happy path, on Berkeley'); +tic('Run DEX with actions, happy path, against real network.'); console.log(); - -let Berkeley = Mina.Network({ - mina: 'https://berkeley.minascan.io/graphql', - archive: 'https://archive-node-api.p42.xyz', +const network = Mina.Network({ + mina: useCustomLocalNetwork + ? 'http://localhost:8080/graphql' + : 'https://berkeley.minascan.io/graphql', + archive: useCustomLocalNetwork + ? 'http://localhost:8282' + : 'https://api.minascan.io/archive/berkeley/v1/graphql', + lightnetAccountManager: 'http://localhost:8181', }); -Mina.setActiveInstance(Berkeley); -let accountFee = Mina.accountCreationFee(); +Mina.setActiveInstance(network); -let tx, pendingTx: Mina.TransactionId, balances, oldBalances; +let tx, pendingTx: Mina.PendingTransaction, balances, oldBalances; // compile contracts & wait for fee payer to be funded -let { sender, senderKey } = await ensureFundedAccount( - 'EKDrVGPC6iVRqB2bMMakNBTdEi8M1TqMn5TViLe9bafcpEExPYui' -); +const senderKey = useCustomLocalNetwork + ? (await Lightnet.acquireKeyPair()).privateKey + : PrivateKey.random(); +const sender = senderKey.toPublicKey(); +if (!useCustomLocalNetwork) { + console.log(`Funding the fee payer account.`); + await ensureFundedAccount(senderKey.toBase58()); +} -TokenContract.analyzeMethods(); -DexTokenHolder.analyzeMethods(); -Dex.analyzeMethods(); +await TokenContract.analyzeMethods(); +await DexTokenHolder.analyzeMethods(); +await Dex.analyzeMethods(); tic('compile (token)'); await TokenContract.compile(); @@ -64,14 +72,15 @@ let userSpec = { sender: addresses.user, fee: 0.1e9 }; if (successfulTransactions <= 0) { tic('deploy & init token contracts'); - tx = await Mina.transaction(senderSpec, () => { + tx = await Mina.transaction(senderSpec, async () => { + await tokenX.deploy(); + await tokenY.deploy(); + // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves - let feePayerUpdate = AccountUpdate.createSigned(sender); - feePayerUpdate.balance.subInPlace(accountFee.mul(2)); - feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee }); - feePayerUpdate.send({ to: addresses.tokenY, amount: accountFee }); - tokenX.deploy(); - tokenY.deploy(); + const accountFee = Mina.getNetworkConstants().accountCreationFee; + let feePayerUpdate = AccountUpdate.fundNewAccount(sender, 2); + feePayerUpdate.send({ to: tokenX.self, amount: accountFee }); + feePayerUpdate.send({ to: tokenY.self, amount: accountFee }); }); await tx.prove(); pendingTx = await tx.sign([senderKey, keys.tokenX, keys.tokenY]).send(); @@ -86,14 +95,16 @@ if (successfulTransactions <= 0) { if (successfulTransactions <= 1) { tic('deploy dex contracts'); - tx = await Mina.transaction(senderSpec, () => { + tx = await Mina.transaction(senderSpec, async () => { // pay fees for creating 3 dex accounts - AccountUpdate.createSigned(sender).balance.subInPlace(accountFee.mul(3)); - dex.deploy(); - dexTokenHolderX.deploy(); - tokenX.approveUpdate(dexTokenHolderX.self); - dexTokenHolderY.deploy(); - tokenY.approveUpdate(dexTokenHolderY.self); + AccountUpdate.createSigned(sender).balance.subInPlace( + Mina.getNetworkConstants().accountCreationFee.mul(3) + ); + await dex.deploy(); + await dexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); pendingTx = await tx.sign([senderKey, keys.dex]).send(); @@ -110,12 +121,20 @@ let USER_DX = 1_000n; if (successfulTransactions <= 2) { tic('transfer tokens to user'); - tx = await Mina.transaction(senderSpec, () => { + tx = await Mina.transaction(senderSpec, async () => { // pay fees for creating 3 user accounts let feePayer = AccountUpdate.fundNewAccount(sender, 3); feePayer.send({ to: addresses.user, amount: 8e9 }); // give users MINA to pay fees - tokenX.transfer(addresses.tokenX, addresses.user, UInt64.from(USER_DX)); - tokenY.transfer(addresses.tokenY, addresses.user, UInt64.from(USER_DX)); + await tokenX.transfer( + addresses.tokenX, + addresses.user, + UInt64.from(USER_DX) + ); + await tokenY.transfer( + addresses.tokenY, + addresses.user, + UInt64.from(USER_DX) + ); }); await tx.prove(); pendingTx = await tx.sign([senderKey, keys.tokenX, keys.tokenY]).send(); @@ -131,9 +150,9 @@ if (successfulTransactions <= 2) { if (successfulTransactions <= 3) { // this is done in advance to avoid account update limit in `supply` tic("create user's lq token account"); - tx = await Mina.transaction(userSpec, () => { + tx = await Mina.transaction(userSpec, async () => { AccountUpdate.fundNewAccount(addresses.user); - dex.createAccount(); + await dex.createAccount(); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); @@ -152,8 +171,8 @@ if (successfulTransactions <= 3) { if (successfulTransactions <= 4) { tic('supply liquidity'); - tx = await Mina.transaction(userSpec, () => { - dex.supplyLiquidityBase(UInt64.from(USER_DX), UInt64.from(USER_DX)); + tx = await Mina.transaction(userSpec, async () => { + await dex.supplyLiquidityBase(UInt64.from(USER_DX), UInt64.from(USER_DX)); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); @@ -174,8 +193,8 @@ let USER_DL = 100n; if (successfulTransactions <= 5) { tic('redeem liquidity, step 1'); - tx = await Mina.transaction(userSpec, () => { - dex.redeemInitialize(UInt64.from(USER_DL)); + tx = await Mina.transaction(userSpec, async () => { + await dex.redeemInitialize(UInt64.from(USER_DL)); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); @@ -192,9 +211,9 @@ if (successfulTransactions <= 5) { if (successfulTransactions <= 6) { tic('redeem liquidity, step 2a (get back token X)'); - tx = await Mina.transaction(userSpec, () => { - dexTokenHolderX.redeemLiquidityFinalize(); - tokenX.approveAny(dexTokenHolderX.self); + tx = await Mina.transaction(userSpec, async () => { + await dexTokenHolderX.redeemLiquidityFinalize(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); @@ -211,9 +230,9 @@ if (successfulTransactions <= 6) { if (successfulTransactions <= 7) { tic('redeem liquidity, step 2b (get back token Y)'); - tx = await Mina.transaction(userSpec, () => { - dexTokenHolderY.redeemLiquidityFinalize(); - tokenY.approveAny(dexTokenHolderY.self); + tx = await Mina.transaction(userSpec, async () => { + await dexTokenHolderY.redeemLiquidityFinalize(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); @@ -235,8 +254,8 @@ if (successfulTransactions <= 8) { tic('swap 10 X for Y'); USER_DX = 10n; - tx = await Mina.transaction(userSpec, () => { - dex.swapX(UInt64.from(USER_DX)); + tx = await Mina.transaction(userSpec, async () => { + await dex.swapX(UInt64.from(USER_DX)); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); @@ -255,6 +274,12 @@ if (successfulTransactions <= 8) { toc(); console.log('dex happy path with actions was successful! 🎉'); +console.log(); +// Tear down +const keyPairReleaseMessage = await Lightnet.releaseKeyPair({ + publicKey: sender.toBase58(), +}); +if (keyPairReleaseMessage) console.info(keyPairReleaseMessage); async function ensureFundedAccount(privateKeyBase58: string) { let senderKey = PrivateKey.fromBase58(privateKeyBase58); @@ -268,10 +293,15 @@ async function ensureFundedAccount(privateKeyBase58: string) { return { senderKey, sender }; } -function logPendingTransaction(pendingTx: Mina.TransactionId) { - if (!pendingTx.isSuccess) throw Error('transaction failed'); +function logPendingTransaction(pendingTx: Mina.PendingTransaction) { + if (pendingTx.status === 'rejected') throw Error('transaction failed'); console.log( - `tx sent: https://berkeley.minaexplorer.com/transaction/${pendingTx.hash()}` + 'tx sent: ' + + (useCustomLocalNetwork + ? `file://${os.homedir()}/.cache/zkapp-cli/lightnet/explorer//index.html?target=transaction&hash=${ + pendingTx.hash + }` + : `https://minascan.io/berkeley/tx/${pendingTx.hash}?type=zk-tx`) ); } diff --git a/src/examples/zkapps/dex/run.ts b/src/examples/zkapps/dex/run.ts index 2fba10cfc0..2b1c6d6a07 100644 --- a/src/examples/zkapps/dex/run.ts +++ b/src/examples/zkapps/dex/run.ts @@ -1,26 +1,14 @@ -import { - isReady, - Mina, - AccountUpdate, - UInt64, - shutdown, - Permissions, - TokenId, -} from 'snarkyjs'; -import { createDex, TokenContract, addresses, keys, tokenIds } from './dex.js'; import { expect } from 'expect'; +import { AccountUpdate, Mina, Permissions, TokenId, UInt64 } from 'o1js'; +import { getProfiler } from '../../utils/profiler.js'; +import { TokenContract, addresses, createDex, keys, tokenIds } from './dex.js'; -import { getProfiler } from '../../profiler.js'; - -await isReady; let proofsEnabled = false; - let Local = Mina.LocalBlockchain({ proofsEnabled, enforceTransactionLimits: false, }); Mina.setActiveInstance(Local); -let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances, oldBalances; @@ -36,7 +24,7 @@ console.log('TOKEN X ID\t', TokenId.toBase58(tokenIds.X)); console.log('TOKEN Y ID\t', TokenId.toBase58(tokenIds.Y)); console.log('-------------------------------------------------'); -TokenContract.analyzeMethods(); +await TokenContract.analyzeMethods(); if (proofsEnabled) { console.log('compile (token)...'); await TokenContract.compile(); @@ -69,8 +57,8 @@ async function main({ withVesting }: { withVesting: boolean }) { let { Dex, DexTokenHolder, getTokenBalances } = createDex(options); // analyze methods for quick error feedback - DexTokenHolder.analyzeMethods(); - Dex.analyzeMethods(); + await DexTokenHolder.analyzeMethods(); + await Dex.analyzeMethods(); if (proofsEnabled) { // compile & deploy all zkApps @@ -87,13 +75,15 @@ async function main({ withVesting }: { withVesting: boolean }) { let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); console.log('deploy & init token contracts...'); - tx = await Mina.transaction(feePayerAddress, () => { - // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves + tx = await Mina.transaction(feePayerAddress, async () => { + await tokenX.deploy(); + await tokenY.deploy(); + + // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves + const accountFee = Mina.getNetworkConstants().accountCreationFee; let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); - feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee.mul(2) }); - feePayerUpdate.send({ to: addresses.tokenY, amount: accountFee.mul(2) }); - tokenX.deploy(); - tokenY.deploy(); + feePayerUpdate.send({ to: tokenX.self, amount: accountFee.mul(2) }); + feePayerUpdate.send({ to: tokenY.self, amount: accountFee.mul(2) }); }); await tx.prove(); tx.sign([feePayerKey, keys.tokenX, keys.tokenY]); @@ -106,14 +96,14 @@ async function main({ withVesting }: { withVesting: boolean }) { ); console.log('deploy dex contracts...'); - tx = await Mina.transaction(feePayerAddress, () => { + tx = await Mina.transaction(feePayerAddress, async () => { // pay fees for creating 3 dex accounts AccountUpdate.fundNewAccount(feePayerAddress, 3); - dex.deploy(); - dexTokenHolderX.deploy(); - tokenX.approveUpdate(dexTokenHolderX.self); - dexTokenHolderY.deploy(); - tokenY.approveUpdate(dexTokenHolderY.self); + await dex.deploy(); + await dexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); tx.sign([feePayerKey, keys.dex]); @@ -121,17 +111,20 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('transfer tokens to user'); tx = await Mina.transaction( - { sender: feePayerAddress, fee: accountFee.mul(1) }, - () => { + { + sender: feePayerAddress, + fee: Mina.getNetworkConstants().accountCreationFee.mul(1), + }, + async () => { let feePayer = AccountUpdate.fundNewAccount(feePayerAddress, 4); feePayer.send({ to: addresses.user, amount: 20e9 }); // give users MINA to pay fees feePayer.send({ to: addresses.user2, amount: 20e9 }); // transfer to fee payer so they can provide initial liquidity - tokenX.transfer(addresses.tokenX, feePayerAddress, UInt64.from(10_000)); - tokenY.transfer(addresses.tokenY, feePayerAddress, UInt64.from(10_000)); + await tokenX.transfer(addresses.tokenX, feePayerAddress, 10_000); + await tokenY.transfer(addresses.tokenY, feePayerAddress, 10_000); // mint tokens to the user (this is additional to the tokens minted at the beginning, so we can overflow the balance - tokenX.init2(); - tokenY.init2(); + await tokenX.init2(); + await tokenY.init2(); } ); await tx.prove(); @@ -144,10 +137,13 @@ async function main({ withVesting }: { withVesting: boolean }) { // supply the initial liquidity where the token ratio can be arbitrary console.log('supply liquidity -- base'); tx = await Mina.transaction( - { sender: feePayerAddress, fee: accountFee }, - () => { + { + sender: feePayerAddress, + fee: Mina.getNetworkConstants().accountCreationFee, + }, + async () => { AccountUpdate.fundNewAccount(feePayerAddress); - dex.supplyLiquidityBase(UInt64.from(10_000), UInt64.from(10_000)); + await dex.supplyLiquidityBase(UInt64.from(10_000), UInt64.from(10_000)); } ); await tx.prove(); @@ -183,9 +179,9 @@ async function main({ withVesting }: { withVesting: boolean }) { */ let USER_DX = 500_000n; console.log('user supply liquidity (1)'); - tx = await Mina.transaction(addresses.user, () => { + tx = await Mina.transaction(addresses.user, async () => { AccountUpdate.fundNewAccount(addresses.user); - dex.supplyLiquidity(UInt64.from(USER_DX)); + await dex.supplyLiquidity(UInt64.from(USER_DX)); }); await tx.prove(); tx.sign([keys.user]); @@ -221,8 +217,8 @@ async function main({ withVesting }: { withVesting: boolean }) { */ USER_DX = 1000n; console.log('user supply liquidity (2)'); - tx = await Mina.transaction(addresses.user, () => { - dex.supplyLiquidity(UInt64.from(USER_DX)); + tx = await Mina.transaction(addresses.user, async () => { + await dex.supplyLiquidity(UInt64.from(USER_DX)); }); await tx.prove(); tx.sign([keys.user]); @@ -250,16 +246,16 @@ async function main({ withVesting }: { withVesting: boolean }) { * - There is not enough tokens available for user’s tokens accounts, one is willing to supply; */ console.log('supplying with no tokens (should fail)'); - tx = await Mina.transaction(addresses.user2, () => { + tx = await Mina.transaction(addresses.user2, async () => { AccountUpdate.fundNewAccount(addresses.user2); - dex.supplyLiquidityBase(UInt64.from(100), UInt64.from(100)); + await dex.supplyLiquidityBase(UInt64.from(100), UInt64.from(100)); }); await tx.prove(); tx.sign([keys.user2]); await expect(tx.send()).rejects.toThrow(/Overflow/); console.log('supplying with insufficient tokens (should fail)'); - tx = await Mina.transaction(addresses.user, () => { - dex.supplyLiquidityBase(UInt64.from(1e9), UInt64.from(1e9)); + tx = await Mina.transaction(addresses.user, async () => { + await dex.supplyLiquidityBase(UInt64.from(1e9), UInt64.from(1e9)); }); await tx.prove(); tx.sign([keys.user]); @@ -273,9 +269,9 @@ async function main({ withVesting }: { withVesting: boolean }) { * => a targeted test with explicitly constructed account updates might be the better strategy to test overflow */ console.log('prepare supplying overflowing liquidity'); - tx = await Mina.transaction(feePayerAddress, () => { + tx = await Mina.transaction(feePayerAddress, async () => { AccountUpdate.fundNewAccount(feePayerAddress); - tokenY.transfer( + await tokenY.transfer( addresses.tokenY, addresses.tokenX, UInt64.MAXINT().sub(200_000) @@ -285,8 +281,8 @@ async function main({ withVesting }: { withVesting: boolean }) { await tx.sign([feePayerKey, keys.tokenY]).send(); console.log('supply overflowing liquidity'); await expect(async () => { - tx = await Mina.transaction(addresses.tokenX, () => { - dex.supplyLiquidityBase( + tx = await Mina.transaction(addresses.tokenX, async () => { + await dex.supplyLiquidityBase( UInt64.MAXINT().sub(200_000), UInt64.MAXINT().sub(200_000) ); @@ -300,7 +296,7 @@ async function main({ withVesting }: { withVesting: boolean }) { * - Value transfer is restricted (supplier end: withdrawal is prohibited, receiver end: receiving is prohibited) for one or both accounts. */ console.log('prepare test with forbidden send'); - tx = await Mina.transaction(addresses.tokenX, () => { + tx = await Mina.transaction(addresses.tokenX, async () => { let tokenXtokenAccount = AccountUpdate.create(addresses.tokenX, tokenIds.X); tokenXtokenAccount.account.permissions.set({ ...Permissions.initial(), @@ -315,9 +311,9 @@ async function main({ withVesting }: { withVesting: boolean }) { await tx.prove(); await tx.sign([keys.tokenX]).send(); console.log('supply with forbidden withdrawal (should fail)'); - tx = await Mina.transaction(addresses.tokenX, () => { + tx = await Mina.transaction(addresses.tokenX, async () => { AccountUpdate.fundNewAccount(addresses.tokenX); - dex.supplyLiquidity(UInt64.from(10)); + await dex.supplyLiquidity(UInt64.from(10)); }); await tx.prove(); await expect(tx.sign([keys.tokenX]).send()).rejects.toThrow( @@ -342,8 +338,8 @@ async function main({ withVesting }: { withVesting: boolean }) { Local.incrementGlobalSlot(1); let USER_DL = 100n; console.log('user redeem liquidity (before liquidity token unlocks)'); - tx = await Mina.transaction(addresses.user, () => { - dex.redeemLiquidity(UInt64.from(USER_DL)); + tx = await Mina.transaction(addresses.user, async () => { + await dex.redeemLiquidity(UInt64.from(USER_DL)); }); await tx.prove(); tx.sign([keys.user]); @@ -366,11 +362,12 @@ async function main({ withVesting }: { withVesting: boolean }) { */ let USER_DL = 100n; console.log('user redeem liquidity'); - tx = await Mina.transaction(addresses.user, () => { - dex.redeemLiquidity(UInt64.from(USER_DL)); + tx = await Mina.transaction(addresses.user, async () => { + await dex.redeemLiquidity(UInt64.from(USER_DL)); }); await tx.prove(); tx.sign([keys.user]); + await tx.send(); [oldBalances, balances] = [balances, getTokenBalances()]; console.log('DEX liquidity (X, Y):', balances.dex.X, balances.dex.Y); @@ -403,8 +400,8 @@ async function main({ withVesting }: { withVesting: boolean }) { if (withVesting) { USER_DX = 1000n; console.log('user supply liquidity -- again, after lock period ended'); - tx = await Mina.transaction(addresses.user, () => { - dex.supplyLiquidity(UInt64.from(USER_DX)); + tx = await Mina.transaction(addresses.user, async () => { + await dex.supplyLiquidity(UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -435,9 +432,9 @@ async function main({ withVesting }: { withVesting: boolean }) { */ USER_DL = 80n; console.log('transfer liquidity tokens to user2'); - tx = await Mina.transaction(addresses.user, () => { + tx = await Mina.transaction(addresses.user, async () => { AccountUpdate.fundNewAccount(addresses.user); - dex.transfer(addresses.user, addresses.user2, UInt64.from(USER_DL)); + await dex.transfer(addresses.user, addresses.user2, UInt64.from(USER_DL)); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -446,12 +443,12 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log( 'redeem liquidity with both users in one tx (fails because of conflicting balance preconditions)' ); - tx = await Mina.transaction(addresses.user2, () => { + tx = await Mina.transaction(addresses.user2, async () => { AccountUpdate.createSigned(addresses.user2).balance.subInPlace( - accountFee.mul(2) + Mina.getNetworkConstants().accountCreationFee.mul(2) ); - dex.redeemLiquidity(UInt64.from(USER_DL)); - dex.redeemLiquidity(UInt64.from(USER_DL)); + await dex.redeemLiquidity(UInt64.from(USER_DL)); + await dex.redeemLiquidity(UInt64.from(USER_DL)); }); await tx.prove(); tx.sign([keys.user, keys.user2]); @@ -460,11 +457,11 @@ async function main({ withVesting }: { withVesting: boolean }) { ); console.log('user2 redeem liquidity'); - tx = await Mina.transaction(addresses.user2, () => { + tx = await Mina.transaction(addresses.user2, async () => { AccountUpdate.createSigned(addresses.user2).balance.subInPlace( - accountFee.mul(2) + Mina.getNetworkConstants().accountCreationFee.mul(2) ); - dex.redeemLiquidity(UInt64.from(USER_DL)); + await dex.redeemLiquidity(UInt64.from(USER_DL)); }); await tx.prove(); await tx.sign([keys.user2]).send(); @@ -486,8 +483,8 @@ async function main({ withVesting }: { withVesting: boolean }) { * note: user2's account is empty now, so redeeming more liquidity fails */ console.log('user2 redeem liquidity (fails because insufficient balance)'); - tx = await Mina.transaction(addresses.user2, () => { - dex.redeemLiquidity(UInt64.from(1n)); + tx = await Mina.transaction(addresses.user2, async () => { + await dex.redeemLiquidity(UInt64.from(1n)); }); await tx.prove(); await expect(tx.sign([keys.user2]).send()).rejects.toThrow(/Overflow/); @@ -508,8 +505,8 @@ async function main({ withVesting }: { withVesting: boolean }) { */ USER_DX = 10n; console.log('swap 10 X for Y'); - tx = await Mina.transaction(addresses.user, () => { - dex.swapX(UInt64.from(USER_DX)); + tx = await Mina.transaction(addresses.user, async () => { + await dex.swapX(UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -521,7 +518,7 @@ async function main({ withVesting }: { withVesting: boolean }) { * - SC withdraws requested amount of X token from user’s account; * - SC sends to user previously calculated amount of Y tokens; * - It will be good to check if calculation was done correctly but correctness is not a major concern since we’re checking - * the zkApps/SnarkyJS on/off-chain features, not the current application's logic; + * the zkApps/o1js on/off-chain features, not the current application's logic; * We're checking the balances of both tokens on caller and SC sides. */ dy = (USER_DX * oldBalances.dex.Y) / (oldBalances.dex.X + USER_DX); @@ -536,5 +533,3 @@ async function main({ withVesting }: { withVesting: boolean }) { DexProfiler.stop().store(); } - -shutdown(); diff --git a/src/examples/zkapps/dex/upgradability.ts b/src/examples/zkapps/dex/upgradability.ts index 2cee0e003c..72835d073d 100644 --- a/src/examples/zkapps/dex/upgradability.ts +++ b/src/examples/zkapps/dex/upgradability.ts @@ -1,33 +1,25 @@ +import { expect } from 'expect'; import { AccountUpdate, Mina, - isReady, Permissions, PrivateKey, UInt64, TransactionVersion, -} from 'snarkyjs'; -import { createDex, TokenContract, addresses, keys, tokenIds } from './dex.js'; -import { expect } from 'expect'; -import { getProfiler } from '../../profiler.js'; - -await isReady; +} from 'o1js'; +import { getProfiler } from '../../utils/profiler.js'; +import { TokenContract, addresses, createDex, keys, tokenIds } from './dex.js'; let proofsEnabled = false; - console.log('starting upgradeability tests'); - await upgradeabilityTests({ withVesting: false, }); console.log('all upgradeability tests were successful! 🎉'); - console.log('starting atomic actions tests'); - await atomicActionsTest({ withVesting: false, }); - console.log('all atomic actions tests were successful!'); async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { @@ -38,7 +30,6 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { enforceTransactionLimits: false, }); Mina.setActiveInstance(Local); - let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances; @@ -47,8 +38,8 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { let { Dex, DexTokenHolder, getTokenBalances } = createDex(options); // analyze methods for quick error feedback - DexTokenHolder.analyzeMethods(); - Dex.analyzeMethods(); + await DexTokenHolder.analyzeMethods(); + await Dex.analyzeMethods(); if (proofsEnabled) { // compile & deploy all zkApps @@ -65,13 +56,15 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); console.log('deploy & init token contracts...'); - tx = await Mina.transaction(feePayerAddress, () => { + tx = await Mina.transaction(feePayerAddress, async () => { + await tokenX.deploy(); + await tokenY.deploy(); + // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves + const accountFee = Mina.getNetworkConstants().accountCreationFee; let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); - feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee.mul(2) }); - feePayerUpdate.send({ to: addresses.tokenY, amount: accountFee.mul(2) }); - tokenX.deploy(); - tokenY.deploy(); + feePayerUpdate.send({ to: tokenX.self, amount: accountFee.mul(2) }); + feePayerUpdate.send({ to: tokenY.self, amount: accountFee.mul(2) }); }); await tx.prove(); tx.sign([feePayerKey, keys.tokenX, keys.tokenY]); @@ -98,14 +91,14 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { */ console.log('deploy dex contracts...'); - tx = await Mina.transaction(feePayerAddress, () => { + tx = await Mina.transaction(feePayerAddress, async () => { // pay fees for creating 3 dex accounts AccountUpdate.fundNewAccount(feePayerAddress, 3); - dex.deploy(); - dexTokenHolderX.deploy(); - tokenX.approveUpdate(dexTokenHolderX.self); - dexTokenHolderY.deploy(); - tokenY.approveUpdate(dexTokenHolderY.self); + await dex.deploy(); + await dexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); console.log('manipulating setDelegate field to impossible...'); // setting the setDelegate permission field to impossible let dexAccount = AccountUpdate.create(addresses.dex); @@ -122,7 +115,7 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { console.log( 'trying to change delegate (setDelegate=impossible, should fail)' ); - tx = await Mina.transaction(feePayerAddress, () => { + tx = await Mina.transaction(feePayerAddress, async () => { // setting the delegate field to something, although permissions forbid it let dexAccount = AccountUpdate.create(addresses.dex); dexAccount.account.delegate.set(PrivateKey.random().toPublicKey()); @@ -136,7 +129,7 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { console.log('changing delegate permission back to normal'); - tx = await Mina.transaction(feePayerAddress, () => { + tx = await Mina.transaction(feePayerAddress, async () => { let dexAccount = AccountUpdate.create(addresses.dex); dexAccount.account.permissions.set({ ...Permissions.initial(), @@ -150,7 +143,7 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { console.log('changing delegate field to a new address'); let newDelegate = PrivateKey.random().toPublicKey(); - tx = await Mina.transaction(feePayerAddress, () => { + tx = await Mina.transaction(feePayerAddress, async () => { let dexAccount = AccountUpdate.create(addresses.dex); dexAccount.account.delegate.set(newDelegate); dexAccount.requireSignature(); @@ -177,7 +170,7 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { 'changing permission to impossible and then trying to change delegate field - in one transaction' ); - tx = await Mina.transaction(feePayerAddress, () => { + tx = await Mina.transaction(feePayerAddress, async () => { // changing the permission to impossible and then trying to change the delegate field let permissionUpdate = AccountUpdate.create(addresses.dex); @@ -193,7 +186,7 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { }); await tx.prove(); await expect(tx.sign([feePayerKey, keys.dex]).send()).rejects.toThrow( - /Update_not_permitted_delegate/ + /Cannot update field 'delegate'/ ); /** @@ -213,7 +206,7 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { console.log('creating multiple valid account updates in one transaction'); newDelegate = PrivateKey.random().toPublicKey(); - tx = await Mina.transaction(feePayerAddress, () => { + tx = await Mina.transaction(feePayerAddress, async () => { // changing field let fieldUpdate = AccountUpdate.create(addresses.dex); fieldUpdate.account.delegate.set(newDelegate); @@ -242,7 +235,6 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { enforceTransactionLimits: false, }); Mina.setActiveInstance(Local); - let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances, oldBalances; @@ -257,8 +249,8 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { } = createDex(options); // analyze methods for quick error feedback - DexTokenHolder.analyzeMethods(); - Dex.analyzeMethods(); + await DexTokenHolder.analyzeMethods(); + await Dex.analyzeMethods(); // compile & deploy all zkApps console.log('compile (token contract)...'); @@ -271,16 +263,19 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { let tokenX = new TokenContract(addresses.tokenX); let tokenY = new TokenContract(addresses.tokenY); let dex = new Dex(addresses.dex); + let dexTokenHolderX = new DexTokenHolder(addresses.dex, tokenIds.X); + let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); console.log('deploy & init token contracts...'); - tx = await Mina.transaction(feePayerAddress, () => { + tx = await Mina.transaction(feePayerAddress, async () => { + await tokenX.deploy(); + await tokenY.deploy(); + // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves - let feePayerUpdate = AccountUpdate.createSigned(feePayerAddress); - feePayerUpdate.balance.subInPlace(accountFee.mul(2)); - feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee.mul(2) }); - feePayerUpdate.send({ to: addresses.tokenY, amount: accountFee.mul(2) }); - tokenX.deploy(); - tokenY.deploy(); + const accountFee = Mina.getNetworkConstants().accountCreationFee; + let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); + feePayerUpdate.send({ to: tokenX.self, amount: accountFee.mul(2) }); + feePayerUpdate.send({ to: tokenY.self, amount: accountFee.mul(2) }); }); await tx.prove(); tx.sign([feePayerKey, keys.tokenX, keys.tokenY]); @@ -311,12 +306,14 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('deploy dex contracts...'); - tx = await Mina.transaction(feePayerKey, () => { + tx = await Mina.transaction(feePayerAddress, async () => { // pay fees for creating 3 dex accounts AccountUpdate.fundNewAccount(feePayerAddress, 3); - dex.deploy(); - tokenX.deployZkapp(addresses.dex, DexTokenHolder._verificationKey!); - tokenY.deployZkapp(addresses.dex, DexTokenHolder._verificationKey!); + await dex.deploy(); + await dexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); tx.sign([feePayerKey, keys.dex]); @@ -324,18 +321,23 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('transfer tokens to user'); tx = await Mina.transaction( - { sender: feePayerAddress, fee: accountFee.mul(1) }, - () => { + { + sender: feePayerAddress, + fee: Mina.getNetworkConstants().accountCreationFee.mul(1), + }, + async () => { let feePayer = AccountUpdate.createSigned(feePayerAddress); - feePayer.balance.subInPlace(Mina.accountCreationFee().mul(4)); + feePayer.balance.subInPlace( + Mina.getNetworkConstants().accountCreationFee.mul(4) + ); feePayer.send({ to: addresses.user, amount: 20e9 }); // give users MINA to pay fees feePayer.send({ to: addresses.user2, amount: 20e9 }); // transfer to fee payer so they can provide initial liquidity - tokenX.transfer(addresses.tokenX, feePayerAddress, UInt64.from(10_000)); - tokenY.transfer(addresses.tokenY, feePayerAddress, UInt64.from(10_000)); + await tokenX.transfer(addresses.tokenX, feePayerAddress, 10_000); + await tokenY.transfer(addresses.tokenY, feePayerAddress, 10_000); // mint tokens to the user (this is additional to the tokens minted at the beginning, so we can overflow the balance - tokenX.init2(); - tokenY.init2(); + await tokenX.init2(); + await tokenY.init2(); } ); await tx.prove(); @@ -355,11 +357,21 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('compiling modified Dex contract...'); await ModifiedDex.compile(); let modifiedDex = new ModifiedDex(addresses.dex); + let modifiedDexTokenHolderX = new ModifiedDexTokenHolder( + addresses.dex, + tokenIds.X + ); + let modifiedDexTokenHolderY = new ModifiedDexTokenHolder( + addresses.dex, + tokenIds.Y + ); - tx = await Mina.transaction(feePayerAddress, () => { - modifiedDex.deploy(); - tokenX.deployZkapp(addresses.dex, ModifiedDexTokenHolder._verificationKey!); - tokenY.deployZkapp(addresses.dex, ModifiedDexTokenHolder._verificationKey!); + tx = await Mina.transaction(feePayerAddress, async () => { + await modifiedDex.deploy(); + await modifiedDexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(modifiedDexTokenHolderX.self); + await modifiedDexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(modifiedDexTokenHolderY.self); }); await tx.prove(); tx.sign([feePayerKey, keys.dex]); @@ -367,11 +379,13 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { // Making sure that both token holder accounts have been updated with the new modified verification key expect( - Mina.getAccount(addresses.dex, tokenX.token.id).zkapp?.verificationKey?.data + Mina.getAccount(addresses.dex, tokenX.deriveTokenId()).zkapp + ?.verificationKey?.data ).toEqual(ModifiedDexTokenHolder._verificationKey?.data); expect( - Mina.getAccount(addresses.dex, tokenY.token.id).zkapp?.verificationKey?.data + Mina.getAccount(addresses.dex, tokenY.deriveTokenId()).zkapp + ?.verificationKey?.data ).toEqual(ModifiedDexTokenHolder._verificationKey?.data); // this is important; we have to re-enable proof production (and verification) to make sure the proofs are valid against the newly deployed VK @@ -379,10 +393,16 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('supply liquidity -- base'); tx = await Mina.transaction( - { sender: feePayerAddress, fee: accountFee.mul(1) }, - () => { + { + sender: feePayerAddress, + fee: Mina.getNetworkConstants().accountCreationFee.mul(1), + }, + async () => { AccountUpdate.fundNewAccount(feePayerAddress); - modifiedDex.supplyLiquidityBase(UInt64.from(10_000), UInt64.from(10_000)); + await modifiedDex.supplyLiquidityBase( + UInt64.from(10_000), + UInt64.from(10_000) + ); } ); await tx.prove(); @@ -393,8 +413,8 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { let USER_DX = 10n; console.log('swap 10 X for Y'); - tx = await Mina.transaction(addresses.user, () => { - modifiedDex.swapX(UInt64.from(USER_DX)); + tx = await Mina.transaction(addresses.user, async () => { + await modifiedDex.swapX(UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -423,7 +443,7 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('changing upgrade permissions to impossible'); - tx = await Mina.transaction(feePayerAddress, () => { + tx = await Mina.transaction(feePayerAddress, async () => { // pay fees for creating 3 dex accounts let update = AccountUpdate.createSigned(addresses.dex); update.account.permissions.set({ @@ -440,8 +460,8 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('trying to upgrade contract - should fail'); - tx = await Mina.transaction(feePayerAddress, () => { - modifiedDex.deploy(); // cannot deploy new VK because its forbidden + tx = await Mina.transaction(feePayerAddress, async () => { + await modifiedDex.deploy(); // cannot deploy new VK because its forbidden }); await tx.prove(); await expect(tx.sign([feePayerKey, keys.dex]).send()).rejects.toThrow( @@ -452,8 +472,8 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { // method should still be valid since the upgrade was forbidden USER_DX = 10n; console.log('swap 10 X for Y'); - tx = await Mina.transaction(addresses.user, () => { - modifiedDex.swapX(UInt64.from(USER_DX)); + tx = await Mina.transaction(addresses.user, async () => { + await modifiedDex.swapX(UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); diff --git a/src/examples/zkapps/escrow/escrow.ts b/src/examples/zkapps/escrow/escrow.ts index 0eaa5d7765..d7e1ea378b 100644 --- a/src/examples/zkapps/escrow/escrow.ts +++ b/src/examples/zkapps/escrow/escrow.ts @@ -1,21 +1,15 @@ -import { - SmartContract, - method, - UInt64, - AccountUpdate, - PublicKey, -} from 'snarkyjs'; +import { SmartContract, method, UInt64, AccountUpdate, PublicKey } from 'o1js'; export class Escrow extends SmartContract { - @method deposit(user: PublicKey) { + @method async deposit(user: PublicKey) { // add your deposit logic circuit here // that will adjust the amount - const payerUpdate = AccountUpdate.create(user); + const payerUpdate = AccountUpdate.createSigned(user); payerUpdate.send({ to: this.address, amount: UInt64.from(1000000) }); } - @method withdraw(user: PublicKey) { + @method async withdraw(user: PublicKey) { // add your withdrawal logic circuit here // that will adjust the amount diff --git a/src/examples/zkapps/hashing/README.md b/src/examples/zkapps/hashing/README.md new file mode 100644 index 0000000000..0193400352 --- /dev/null +++ b/src/examples/zkapps/hashing/README.md @@ -0,0 +1,5 @@ +Hash functions under the `Hash` namespace in o1js require binary arithmetic and operate over binary data. + +This example shows how to extend the `Bytes` class and specify the length of bytes. + +See the [Keccak](https://docs.minaprotocol.com/zkapps/o1js/keccak) and [SHA-256](https://docs.minaprotocol.com/zkapps/o1js/sha256) documentation for o1js. diff --git a/src/examples/zkapps/hashing/hash.ts b/src/examples/zkapps/hashing/hash.ts new file mode 100644 index 0000000000..3dbb62cef7 --- /dev/null +++ b/src/examples/zkapps/hashing/hash.ts @@ -0,0 +1,50 @@ +import { + Hash, + Field, + SmartContract, + state, + State, + method, + Permissions, + Bytes, +} from 'o1js'; + +let initialCommitment = Field(0); +class Bytes32 extends Bytes(32) {} + +export class HashStorage extends SmartContract { + @state(Field) commitment = State(); + + init() { + super.init(); + this.account.permissions.set({ + ...Permissions.default(), + editState: Permissions.proofOrSignature(), + }); + this.commitment.set(initialCommitment); + } + + @method async SHA256(xs: Bytes32) { + const shaHash = Hash.SHA3_256.hash(xs); + const commitment = Hash.hash(shaHash.toFields()); + this.commitment.set(commitment); + } + + @method async SHA384(xs: Bytes32) { + const shaHash = Hash.SHA3_384.hash(xs); + const commitment = Hash.hash(shaHash.toFields()); + this.commitment.set(commitment); + } + + @method async SHA512(xs: Bytes32) { + const shaHash = Hash.SHA3_512.hash(xs); + const commitment = Hash.hash(shaHash.toFields()); + this.commitment.set(commitment); + } + + @method async Keccak256(xs: Bytes32) { + const shaHash = Hash.Keccak256.hash(xs); + const commitment = Hash.hash(shaHash.toFields()); + this.commitment.set(commitment); + } +} diff --git a/src/examples/zkapps/hashing/run.ts b/src/examples/zkapps/hashing/run.ts new file mode 100644 index 0000000000..ac25f6868b --- /dev/null +++ b/src/examples/zkapps/hashing/run.ts @@ -0,0 +1,73 @@ +import { HashStorage } from './hash.js'; +import { Mina, PrivateKey, AccountUpdate, Bytes } from 'o1js'; + +let txn; +let proofsEnabled = true; +// setup local ledger +let Local = Mina.LocalBlockchain({ proofsEnabled }); +Mina.setActiveInstance(Local); + +if (proofsEnabled) { + console.log('Proofs enabled'); + await HashStorage.compile(); +} + +// test accounts that pays all the fees, and puts additional funds into the zkapp +const feePayer = Local.testAccounts[0]; + +// zkapp account +const zkAppPrivateKey = PrivateKey.random(); +const zkAppAddress = zkAppPrivateKey.toPublicKey(); +const zkAppInstance = new HashStorage(zkAppAddress); + +// 0, 1, 2, 3, ..., 31 +const hashData = Bytes.from(Array.from({ length: 32 }, (_, i) => i)); + +console.log('Deploying Hash Example....'); +txn = await Mina.transaction(feePayer.publicKey, async () => { + AccountUpdate.fundNewAccount(feePayer.publicKey); + await zkAppInstance.deploy(); +}); +await txn.sign([feePayer.privateKey, zkAppPrivateKey]).send(); + +const initialState = + Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); + +let currentState; +console.log('Initial State', initialState); + +console.log(`Updating commitment from ${initialState} using SHA256 ...`); +txn = await Mina.transaction(feePayer.publicKey, async () => { + await zkAppInstance.SHA256(hashData); +}); +await txn.prove(); +await txn.sign([feePayer.privateKey]).send(); +currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); +console.log(`Current state successfully updated to ${currentState}`); + +console.log(`Updating commitment from ${initialState} using SHA384 ...`); +txn = await Mina.transaction(feePayer.publicKey, async () => { + await zkAppInstance.SHA384(hashData); +}); +await txn.prove(); +await txn.sign([feePayer.privateKey]).send(); +currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); +console.log(`Current state successfully updated to ${currentState}`); + +console.log(`Updating commitment from ${initialState} using SHA512 ...`); +txn = await Mina.transaction(feePayer.publicKey, async () => { + await zkAppInstance.SHA512(hashData); +}); +await txn.prove(); +await txn.sign([feePayer.privateKey]).send(); +currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); +console.log(`Current state successfully updated to ${currentState}`); + +console.log(`Updating commitment from ${initialState} using Keccak256...`); +txn = await Mina.transaction(feePayer.publicKey, async () => { + await zkAppInstance.Keccak256(hashData); +}); +await txn.prove(); +await txn.sign([feePayer.privateKey]).send(); +currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); +console.log(`Current state successfully updated to ${currentState}`); diff --git a/src/examples/zkapps/hello_world/hello_world.ts b/src/examples/zkapps/hello-world/hello-world.ts similarity index 55% rename from src/examples/zkapps/hello_world/hello_world.ts rename to src/examples/zkapps/hello-world/hello-world.ts index ce686d4e97..6da2800f01 100644 --- a/src/examples/zkapps/hello_world/hello_world.ts +++ b/src/examples/zkapps/hello-world/hello-world.ts @@ -1,16 +1,13 @@ import { Field, + PrivateKey, + Provable, SmartContract, - state, State, + assert, method, - DeployArgs, - PrivateKey, - Permissions, - isReady, -} from 'snarkyjs'; - -await isReady; + state, +} from 'o1js'; export const adminPrivateKey = PrivateKey.random(); export const adminPublicKey = adminPrivateKey.toPublicKey(); @@ -24,14 +21,20 @@ export class HelloWorld extends SmartContract { this.account.delegate.set(adminPublicKey); } - @method update(squared: Field, admin: PrivateKey) { - const x = this.x.get(); - this.x.assertNothing(); + @method async update(squared: Field, admin: PrivateKey) { + // explicitly fetch state from the chain + const x = await Provable.witnessAsync(Field, async () => { + let x = await this.x.fetch(); + assert(x !== undefined, 'x can be fetched'); + return x; + }); + + this.x.requireNothing(); x.square().assertEquals(squared); this.x.set(squared); const adminPk = admin.toPublicKey(); - this.account.delegate.assertEquals(adminPk); + this.account.delegate.requireEquals(adminPk); } } diff --git a/src/examples/zkapps/hello-world/run-live.ts b/src/examples/zkapps/hello-world/run-live.ts new file mode 100644 index 0000000000..3f63a9ae94 --- /dev/null +++ b/src/examples/zkapps/hello-world/run-live.ts @@ -0,0 +1,108 @@ +// Live integration test against real Mina network. +import { + AccountUpdate, + Field, + Lightnet, + Mina, + PrivateKey, + fetchAccount, +} from 'o1js'; +import { HelloWorld, adminPrivateKey } from './hello-world.js'; + +const useCustomLocalNetwork = process.env.USE_CUSTOM_LOCAL_NETWORK === 'true'; +const zkAppKey = PrivateKey.random(); +const zkAppAddress = zkAppKey.toPublicKey(); +const transactionFee = 100_000_000; + +// Network configuration +const network = Mina.Network({ + mina: useCustomLocalNetwork + ? 'http://localhost:8080/graphql' + : 'https://proxy.berkeley.minaexplorer.com/graphql', + lightnetAccountManager: 'http://localhost:8181', +}); +Mina.setActiveInstance(network); + +// Fee payer setup +const senderKey = useCustomLocalNetwork + ? (await Lightnet.acquireKeyPair()).privateKey + : PrivateKey.random(); +const sender = senderKey.toPublicKey(); +if (!useCustomLocalNetwork) { + console.log(`Funding the fee payer account.`); + await Mina.faucet(sender); +} +console.log(`Fetching the fee payer account information.`); +const accountDetails = (await fetchAccount({ publicKey: sender })).account; +console.log( + `Using the fee payer account ${sender.toBase58()} with nonce: ${ + accountDetails?.nonce + } and balance: ${accountDetails?.balance}.` +); +console.log(''); + +// zkApp compilation +console.log('Compiling the smart contract.'); +const { verificationKey } = await HelloWorld.compile(); +const zkApp = new HelloWorld(zkAppAddress); +console.log(''); + +// zkApp deployment +console.log(`Deploying zkApp for public key ${zkAppAddress.toBase58()}.`); +let transaction = await Mina.transaction( + { sender, fee: transactionFee }, + async () => { + AccountUpdate.fundNewAccount(sender); + await zkApp.deploy({ verificationKey }); + } +); +transaction.sign([senderKey, zkAppKey]); +console.log('Sending the transaction.'); +let pendingTx = await transaction.send(); +if (pendingTx.status === 'pending') { + console.log(`Success! Deploy transaction sent. +Your smart contract will be deployed +as soon as the transaction is included in a block. +Txn hash: ${pendingTx.hash}`); +} +console.log('Waiting for transaction inclusion in a block.'); +await pendingTx.wait({ maxAttempts: 90 }); +console.log(''); + +// zkApp state update +console.log('Trying to update deployed zkApp state.'); +transaction = await Mina.transaction( + { sender, fee: transactionFee }, + async () => { + await zkApp.update(Field(4), adminPrivateKey); + } +); +await transaction.sign([senderKey]).prove(); +console.log('Sending the transaction.'); +pendingTx = await transaction.send(); +if (pendingTx.status === 'pending') { + console.log(`Success! Update transaction sent. +Your smart contract state will be updated +as soon as the transaction is included in a block. +Txn hash: ${pendingTx.hash}`); +} +console.log('Waiting for transaction inclusion in a block.'); +await pendingTx.wait({ maxAttempts: 90 }); +console.log(''); + +// zkApp state check +console.log('Validating zkApp state update.'); +try { + (await zkApp.x.fetch())?.assertEquals(Field(4)); +} catch (error) { + throw new Error( + `On-chain zkApp account state doesn't match the expected state. ${error}` + ); +} +console.log('Success!'); + +// Tear down +const keyPairReleaseMessage = await Lightnet.releaseKeyPair({ + publicKey: sender.toBase58(), +}); +if (keyPairReleaseMessage) console.info(keyPairReleaseMessage); diff --git a/src/examples/zkapps/hello_world/run.ts b/src/examples/zkapps/hello-world/run.ts similarity index 81% rename from src/examples/zkapps/hello_world/run.ts rename to src/examples/zkapps/hello-world/run.ts index 9be1b6fc5d..929a5da800 100644 --- a/src/examples/zkapps/hello_world/run.ts +++ b/src/examples/zkapps/hello-world/run.ts @@ -1,6 +1,6 @@ -import { HelloWorld, adminPrivateKey } from './hello_world.js'; -import { Mina, PrivateKey, AccountUpdate, Field } from 'snarkyjs'; -import { getProfiler } from '../../profiler.js'; +import { AccountUpdate, Field, Mina, PrivateKey } from 'o1js'; +import { getProfiler } from '../../utils/profiler.js'; +import { HelloWorld, adminPrivateKey } from './hello-world.js'; const HelloWorldProfier = getProfiler('Hello World'); HelloWorldProfier.start('Hello World test flow'); @@ -23,9 +23,9 @@ const zkAppInstance = new HelloWorld(zkAppAddress); console.log('Deploying Hello World ....'); -txn = await Mina.transaction(feePayer1.publicKey, () => { +txn = await Mina.transaction(feePayer1.publicKey, async () => { AccountUpdate.fundNewAccount(feePayer1.publicKey); - zkAppInstance.deploy(); + await zkAppInstance.deploy(); }); await txn.sign([feePayer1.privateKey, zkAppPrivateKey]).send(); @@ -41,8 +41,8 @@ console.log( `Updating state from ${initialState} to 4 with Admin Private Key ...` ); -txn = await Mina.transaction(feePayer1.publicKey, () => { - zkAppInstance.update(Field(4), adminPrivateKey); +txn = await Mina.transaction(feePayer1.publicKey, async () => { + await zkAppInstance.update(Field(4), adminPrivateKey); }); await txn.prove(); await txn.sign([feePayer1.privateKey]).send(); @@ -66,8 +66,8 @@ console.log( let correctlyFails = false; try { - txn = await Mina.transaction(feePayer1.publicKey, () => { - zkAppInstance.update(Field(16), wrongAdminPrivateKey); + txn = await Mina.transaction(feePayer1.publicKey, async () => { + await zkAppInstance.update(Field(16), wrongAdminPrivateKey); }); await txn.prove(); await txn.sign([feePayer1.privateKey]).send(); @@ -87,8 +87,8 @@ try { `Attempting to update state from ${currentState} to the value that fails precondition of 30 ...` ); - txn = await Mina.transaction(feePayer1.publicKey, () => { - zkAppInstance.update(Field(30), adminPrivateKey); + txn = await Mina.transaction(feePayer1.publicKey, async () => { + await zkAppInstance.update(Field(30), adminPrivateKey); }); await txn.prove(); await txn.sign([feePayer1.privateKey]).send(); @@ -113,8 +113,8 @@ try { // expected to fail and current state stays at 4 txn = await Mina.transaction( { sender: feePayer1.publicKey, fee: '10' }, - () => { - zkAppInstance.update(Field(256), adminPrivateKey); + async () => { + await zkAppInstance.update(Field(256), adminPrivateKey); } ); await txn.prove(); @@ -130,9 +130,12 @@ if (!correctlyFails) { } // expected to succeed and update state to 16 -txn2 = await Mina.transaction({ sender: feePayer2.publicKey, fee: '2' }, () => { - zkAppInstance.update(Field(16), adminPrivateKey); -}); +txn2 = await Mina.transaction( + { sender: feePayer2.publicKey, fee: '2' }, + async () => { + await zkAppInstance.update(Field(16), adminPrivateKey); + } +); await txn2.prove(); await txn2.sign([feePayer2.privateKey]).send(); @@ -147,9 +150,12 @@ if (currentState !== '16') { console.log(`Update successful. Current state is ${currentState}.`); // expected to succeed and update state to 256 -txn3 = await Mina.transaction({ sender: feePayer3.publicKey, fee: '1' }, () => { - zkAppInstance.update(Field(256), adminPrivateKey); -}); +txn3 = await Mina.transaction( + { sender: feePayer3.publicKey, fee: '1' }, + async () => { + await zkAppInstance.update(Field(256), adminPrivateKey); + } +); await txn3.prove(); await txn3.sign([feePayer3.privateKey]).send(); @@ -169,8 +175,8 @@ try { // expected to fail and current state remains 256 txn4 = await Mina.transaction( { sender: feePayer4.publicKey, fee: '1' }, - () => { - zkAppInstance.update(Field(16), adminPrivateKey); + async () => { + await zkAppInstance.update(Field(16), adminPrivateKey); } ); await txn4.prove(); diff --git a/src/examples/zkapps/hello_world/run_berkeley.ts b/src/examples/zkapps/hello_world/run_berkeley.ts deleted file mode 100644 index 819fe1f8ef..0000000000 --- a/src/examples/zkapps/hello_world/run_berkeley.ts +++ /dev/null @@ -1,101 +0,0 @@ -// live Berkeley integration test -import { - Field, - PrivateKey, - Mina, - AccountUpdate, - isReady, - shutdown, -} from 'snarkyjs'; -import { adminPrivateKey, HelloWorld } from './hello_world.js'; -await isReady; - -let Berkeley = Mina.Network('https://proxy.berkeley.minaexplorer.com/graphql'); -Mina.setActiveInstance(Berkeley); - -let senderKey = PrivateKey.random(); -let sender = senderKey.toPublicKey(); - -console.log( - `Funding fee payer ${senderKey - .toPublicKey() - .toBase58()} and waiting for inclusion in a block..` -); - -await Mina.faucet(sender); - -let { nonce, balance } = Berkeley.getAccount(sender); -console.log( - `Using fee payer ${sender.toBase58()} with nonce ${nonce}, balance ${balance}` -); - -let zkappKey = PrivateKey.random(); -let zkappAddress = zkappKey.toPublicKey(); - -let transactionFee = 100_000_000; - -console.log('Compiling smart contract..'); -let { verificationKey } = await HelloWorld.compile(); - -let zkapp = new HelloWorld(zkappAddress); - -console.log(`Deploying zkapp for public key ${zkappAddress.toBase58()}.`); - -let transaction = await Mina.transaction( - { sender, fee: transactionFee }, - () => { - AccountUpdate.fundNewAccount(sender); - zkapp.deploy({ verificationKey }); - } -); -transaction.sign([senderKey, zkappKey]); - -console.log('Sending the transaction..'); -let pendingTx = await transaction.send(); -if (pendingTx.hash() !== undefined) { - console.log(` -Success! Deploy transaction sent. - -Your smart contract state will be deployed -as soon as the transaction is included in a block: -https://berkeley.minaexplorer.com/transaction/${pendingTx.hash()} -`); -} - -console.log('Waiting for transaction inclusion..'); -await pendingTx.wait({ maxAttempts: 90 }); - -console.log('Trying to update deployed zkApp..'); - -transaction = await Mina.transaction({ sender, fee: transactionFee }, () => { - zkapp.update(Field(4), adminPrivateKey); -}); -await transaction.sign([senderKey]).prove(); - -console.log('Sending the transaction..'); -pendingTx = await transaction.send(); - -if (pendingTx.hash() !== undefined) { - console.log(` -Success! Update transaction sent. - -Your smart contract state will be updated -as soon as the transaction is included in a block: -https://berkeley.minaexplorer.com/transaction/${pendingTx.hash()} -`); -} -console.log('Waiting for transaction inclusion..'); -await pendingTx.wait({ maxAttempts: 90 }); - -console.log('Checking if the update was valid..'); - -try { - (await zkapp.x.fetch())?.assertEquals(Field(4)); -} catch (error) { - throw new Error( - `On-chain zkApp account doesn't match the expected state. ${error}` - ); -} -console.log('Success!'); - -shutdown(); diff --git a/src/examples/zkapps/local_events_zkapp.ts b/src/examples/zkapps/local-events-zkapp.ts similarity index 82% rename from src/examples/zkapps/local_events_zkapp.ts rename to src/examples/zkapps/local-events-zkapp.ts index 0af796df49..e3daf2b574 100644 --- a/src/examples/zkapps/local_events_zkapp.ts +++ b/src/examples/zkapps/local-events-zkapp.ts @@ -3,21 +3,17 @@ import { state, State, method, - UInt64, PrivateKey, SmartContract, Mina, AccountUpdate, - isReady, UInt32, PublicKey, Struct, -} from 'snarkyjs'; +} from 'o1js'; const doProofs = false; -await isReady; - class Event extends Struct({ pub: PublicKey, value: Field }) {} class SimpleZkapp extends SmartContract { @@ -33,14 +29,14 @@ class SimpleZkapp extends SmartContract { this.x.set(initialState); } - @method update(y: Field) { + @method async update(y: Field) { this.emitEvent('complexEvent', { pub: PrivateKey.random().toPublicKey(), value: y, }); this.emitEvent('simpleEvent', y); let x = this.x.get(); - this.x.assertEquals(x); + this.x.requireEquals(x); this.x.set(x.add(y)); } } @@ -65,22 +61,22 @@ if (doProofs) { } console.log('deploy'); -let tx = await Mina.transaction(feePayer, () => { +let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); + await zkapp.deploy(); }); await tx.sign([feePayerKey, zkappKey]).send(); console.log('call update'); -tx = await Mina.transaction(feePayer, () => { - zkapp.update(Field(1)); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.update(Field(1)); }); await tx.prove(); await tx.sign([feePayerKey]).send(); console.log('call update'); -tx = await Mina.transaction(feePayer, () => { - zkapp.update(Field(2)); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.update(Field(2)); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -91,7 +87,7 @@ let events = await zkapp.fetchEvents(UInt32.from(0)); console.log(events); console.log('---- emitted events: ----'); // fetches all events from zkapp starting block height 0 and ending at block height 10 -events = await zkapp.fetchEvents(UInt32.from(0), UInt64.from(10)); +events = await zkapp.fetchEvents(UInt32.from(0), UInt32.from(10)); console.log(events); console.log('---- emitted events: ----'); // fetches all events diff --git a/src/examples/zkapps/merkle_tree/merkle_zkapp.ts b/src/examples/zkapps/merkle-tree/merkle-zkapp.ts similarity index 92% rename from src/examples/zkapps/merkle_tree/merkle_zkapp.ts rename to src/examples/zkapps/merkle-tree/merkle-zkapp.ts index 0832bf7708..c1adc8e17a 100644 --- a/src/examples/zkapps/merkle_tree/merkle_zkapp.ts +++ b/src/examples/zkapps/merkle-tree/merkle-zkapp.ts @@ -25,7 +25,7 @@ import { MerkleTree, MerkleWitness, Struct, -} from 'snarkyjs'; +} from 'o1js'; const doProofs = true; @@ -59,13 +59,13 @@ class Leaderboard extends SmartContract { // a commitment is a cryptographic primitive that allows us to commit to data, with the ability to "reveal" it later @state(Field) commitment = State(); - @method init() { + @method async init() { super.init(); this.commitment.set(initialCommitment); } @method - guessPreimage(guess: Field, account: Account, path: MyMerkleWitness) { + async guessPreimage(guess: Field, account: Account, path: MyMerkleWitness) { // this is our hash! its the hash of the preimage "22", but keep it a secret! let target = Field( '17057234437185175411792943285768571642343179330449434169483610110583519635705' @@ -75,7 +75,7 @@ class Leaderboard extends SmartContract { // we fetch the on-chain commitment let commitment = this.commitment.get(); - this.commitment.assertEquals(commitment); + this.commitment.requireEquals(commitment); // we check that the account is within the committed Merkle Tree path.calculateRoot(account.hash()).assertEquals(commitment); @@ -133,12 +133,12 @@ console.log('Deploying leaderboard..'); if (doProofs) { await Leaderboard.compile(); } -let tx = await Mina.transaction(feePayer, () => { +let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer).send({ to: zkappAddress, amount: initialBalance, }); - leaderboardZkApp.deploy(); + await leaderboardZkApp.deploy(); }); await tx.prove(); await tx.sign([feePayerKey, zkappKey]).send(); @@ -155,8 +155,8 @@ async function makeGuess(name: Names, index: bigint, guess: number) { let w = Tree.getWitness(index); let witness = new MyMerkleWitness(w); - let tx = await Mina.transaction(feePayer, () => { - leaderboardZkApp.guessPreimage(Field(guess), account, witness); + let tx = await Mina.transaction(feePayer, async () => { + await leaderboardZkApp.guessPreimage(Field(guess), account, witness); }); await tx.prove(); await tx.sign([feePayerKey, zkappKey]).send(); diff --git a/src/examples/zkapps/reducer/actions-as-merkle-list.ts b/src/examples/zkapps/reducer/actions-as-merkle-list.ts new file mode 100644 index 0000000000..16ce8ea29c --- /dev/null +++ b/src/examples/zkapps/reducer/actions-as-merkle-list.ts @@ -0,0 +1,148 @@ +/** + * This example shows how to iterate through incoming actions, not using `Reducer.reduce` but by + * treating the actions as a merkle list. + * + * This is mainly intended as an example for using `MerkleList`, but it might also be useful as + * a blueprint for processing actions in a custom and more explicit way. + */ +import { + AccountUpdate, + Bool, + Field, + MerkleList, + Mina, + Provable, + PublicKey, + Reducer, + SmartContract, + method, + assert, +} from 'o1js'; + +const { Actions } = AccountUpdate; + +// in this example, an action is just a public key +type Action = PublicKey; +const Action = PublicKey; + +// the actions within one account update are a Merkle list with a custom hash +const emptyHash = Actions.empty().hash; +const nextHash = (hash: Field, action: Action) => + Actions.pushEvent({ hash, data: [] }, action.toFields()).hash; + +class MerkleActions extends MerkleList.create(Action, nextHash, emptyHash) {} + +// the "action state" / actions from many account updates is a Merkle list +// of the above Merkle list, with another custom hash +let emptyActionsHash = Actions.emptyActionState(); +const nextActionsHash = (hash: Field, actions: MerkleActions) => + Actions.updateSequenceState(hash, actions.hash); + +class MerkleActionss extends MerkleList.create( + MerkleActions.provable, + nextActionsHash, + emptyActionsHash +) {} + +// constants for our static-sized provable code +const MAX_UPDATES_WITH_ACTIONS = 100; +const MAX_ACTIONS_PER_UPDATE = 2; + +/** + * This contract allows you to push either 1 or 2 public keys as actions, + * and has a reducer-like method which checks whether a given public key is contained in those actions. + */ +class ActionsContract extends SmartContract { + reducer = Reducer({ actionType: Action }); + + @method + async postAddress(address: PublicKey) { + this.reducer.dispatch(address); + } + + // to exhibit the generality of reducer: can dispatch more than 1 action per account update + @method async postTwoAddresses(a1: PublicKey, a2: PublicKey) { + this.reducer.dispatch(a1); + this.reducer.dispatch(a2); + } + + @method + async assertContainsAddress(address: PublicKey) { + // get actions and, in a witness block, wrap them in a Merkle list of lists + + // note: need to reverse here because `getActions()` returns the last pushed action last, + // but MerkleList.from() wants it to be first to match the natural iteration order + let actionss = this.reducer.getActions().reverse(); + + let merkleActionss = Provable.witness(MerkleActionss.provable, () => + MerkleActionss.from(actionss.map((as) => MerkleActions.from(as))) + ); + + // prove that we know the correct action state + this.account.actionState.requireEquals(merkleActionss.hash); + + // now our provable code to process the actions is very straight-forward + // (note: if we're past the actual sizes, `.pop()` returns a dummy Action -- in this case, the "empty" public key which is not equal to any real address) + let hasAddress = Bool(false); + + for (let i = 0; i < MAX_UPDATES_WITH_ACTIONS; i++) { + let merkleActions = merkleActionss.pop(); + + for (let j = 0; j < MAX_ACTIONS_PER_UPDATE; j++) { + let action = merkleActions.pop(); + hasAddress = hasAddress.or(action.equals(address)); + } + } + + assert(hasAddress); + } +} + +// TESTS + +// set up a local blockchain + +let Local = Mina.LocalBlockchain({ proofsEnabled: false }); +Mina.setActiveInstance(Local); + +let [ + { publicKey: sender, privateKey: senderKey }, + { publicKey: zkappAddress, privateKey: zkappKey }, + { publicKey: otherAddress }, + { publicKey: anotherAddress }, +] = Local.testAccounts; + +let zkapp = new ActionsContract(zkappAddress); + +// deploy the contract + +await ActionsContract.compile(); +console.log( + `rows for ${MAX_UPDATES_WITH_ACTIONS} updates with actions`, + (await ActionsContract.analyzeMethods()).assertContainsAddress.rows +); +let deployTx = await Mina.transaction(sender, async () => zkapp.deploy()); +await deployTx.sign([senderKey, zkappKey]).send(); + +// push some actions + +let dispatchTx = await Mina.transaction(sender, async () => { + await zkapp.postAddress(otherAddress); + await zkapp.postAddress(zkappAddress); + await zkapp.postTwoAddresses(anotherAddress, sender); + await zkapp.postAddress(anotherAddress); + await zkapp.postTwoAddresses(zkappAddress, otherAddress); +}); +await dispatchTx.prove(); +await dispatchTx.sign([senderKey]).send(); + +assert(zkapp.reducer.getActions().length === 5); + +// check if the actions contain the `sender` address + +Local.setProofsEnabled(true); +let containsTx = await Mina.transaction(sender, () => + zkapp.assertContainsAddress(sender) +); +await containsTx.prove(); +await containsTx.sign([senderKey]).send(); diff --git a/src/examples/zkapps/reducer/map.ts b/src/examples/zkapps/reducer/map.ts new file mode 100644 index 0000000000..70d7561ca5 --- /dev/null +++ b/src/examples/zkapps/reducer/map.ts @@ -0,0 +1,196 @@ +import { + Field, + Struct, + method, + PrivateKey, + SmartContract, + Mina, + AccountUpdate, + Reducer, + provable, + PublicKey, + Bool, + Poseidon, + Provable, + assert, +} from 'o1js'; + +/* + +This contract emulates a "mapping" data structure, which is a key-value store, similar to a dictionary or hash table or `new Map()` in JavaScript. +In this example, the keys are public keys, and the values are arbitrary field elements. + +This utilizes the `Reducer` as an append online list of actions, which are then looked at to find the value corresponding to a specific key. + + +```ts +// js +const map = new Map(); +map.set(key, value); +map.get(key); + +// contract +await zkApp.deploy(); // ... deploy the zkapp +await zkApp.set(key, value); // ... set a key-value pair +await zkApp.get(key); // ... get a value by key +``` +*/ + +class Option extends Struct({ + isSome: Bool, + value: Field, +}) {} + +const KeyValuePair = provable({ + key: Field, + value: Field, +}); + +class StorageContract extends SmartContract { + reducer = Reducer({ + actionType: KeyValuePair, + }); + + @method async set(key: PublicKey, value: Field) { + this.reducer.dispatch({ key: Poseidon.hash(key.toFields()), value }); + } + + @method.returns(Option) + async get(key: PublicKey) { + let pendingActions = this.reducer.getActions({ + fromActionState: Reducer.initialActionState, + }); + + let keyHash = Poseidon.hash(key.toFields()); + + let { state: optionValue } = this.reducer.reduce( + pendingActions, + Option, + (state, action) => { + let currentMatch = keyHash.equals(action.key); + return { + isSome: currentMatch.or(state.isSome), + value: Provable.if(currentMatch, action.value, state.value), + }; + }, + { + state: Option.empty(), + actionState: Reducer.initialActionState, + }, + { maxTransactionsWithActions: k } + ); + + return optionValue; + } +} + +let k = 1 << 4; + +let Local = Mina.LocalBlockchain(); +Mina.setActiveInstance(Local); +let cs = await StorageContract.analyzeMethods(); + +console.log(`method size for a "mapping" contract with ${k} entries`); +console.log('get rows:', cs['get'].rows); +console.log('set rows:', cs['set'].rows); + +// a test account that pays all the fees +let feePayerKey = Local.testAccounts[0].privateKey; +let feePayer = Local.testAccounts[0].publicKey; + +// the zkapp account +let zkappKey = PrivateKey.random(); +let zkappAddress = zkappKey.toPublicKey(); +let zkapp = new StorageContract(zkappAddress); + +await StorageContract.compile(); + +let tx = await Mina.transaction(feePayer, async () => { + AccountUpdate.fundNewAccount(feePayer); + await zkapp.deploy(); +}); +await tx.sign([feePayerKey, zkappKey]).send(); + +console.log('deployed'); + +let map: { key: PublicKey; value: Field }[] = [ + { + key: PrivateKey.random().toPublicKey(), + value: Field(192), + }, + { + key: PrivateKey.random().toPublicKey(), + value: Field(151), + }, + { + key: PrivateKey.random().toPublicKey(), + value: Field(781), + }, +]; + +let key = map[0].key; +let value = map[0].value; +console.log(`setting key ${key.toBase58()} with value ${value}`); + +tx = await Mina.transaction(feePayer, async () => { + await zkapp.set(key, value); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +key = map[1].key; +value = map[1].value; +console.log(`setting key ${key.toBase58()} with value ${value}`); + +tx = await Mina.transaction(feePayer, async () => { + await zkapp.set(key, value); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +key = map[2].key; +value = map[2].value; +console.log(`setting key ${key.toBase58()} with value ${value}`); + +tx = await Mina.transaction(feePayer, async () => { + await zkapp.set(key, value); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +key = map[0].key; +value = map[0].value; +console.log(`getting key ${key.toBase58()} with value ${value}`); + +let result: Option | undefined; +tx = await Mina.transaction(feePayer, async () => { + result = await zkapp.get(key); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +assert(result !== undefined); +console.log('found correct match?', result.isSome.toBoolean()); +console.log('matches expected value?', result.value.equals(value).toBoolean()); + +key = map[1].key; +value = map[1].value; +console.log(`getting key ${key.toBase58()} with value ${value}`); + +tx = await Mina.transaction(feePayer, async () => { + result = await zkapp.get(key); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +console.log('found correct match?', result.isSome.toBoolean()); +console.log('matches expected value?', result.value.equals(value).toBoolean()); + +console.log(`getting key invalid key`); +tx = await Mina.transaction(feePayer, async () => { + result = await zkapp.get(PrivateKey.random().toPublicKey()); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +console.log('should be isSome(false)', result.isSome.toBoolean()); diff --git a/src/examples/zkapps/reducer/reducer_composite.ts b/src/examples/zkapps/reducer/reducer-composite.ts similarity index 79% rename from src/examples/zkapps/reducer/reducer_composite.ts rename to src/examples/zkapps/reducer/reducer-composite.ts index 28296f596b..b299d20e6e 100644 --- a/src/examples/zkapps/reducer/reducer_composite.ts +++ b/src/examples/zkapps/reducer/reducer-composite.ts @@ -7,16 +7,13 @@ import { SmartContract, Mina, AccountUpdate, - isReady, Bool, Struct, Reducer, Provable, -} from 'snarkyjs'; +} from 'o1js'; import assert from 'node:assert/strict'; -import { getProfiler } from '../../profiler.js'; - -await isReady; +import { getProfiler } from '../../utils/profiler.js'; class MaybeIncrement extends Struct({ isIncrement: Bool, @@ -34,19 +31,19 @@ class CounterZkapp extends SmartContract { // helper field to store the point in the action history that our on-chain state is at @state(Field) actionState = State(); - @method incrementCounter() { + @method async incrementCounter() { this.reducer.dispatch(INCREMENT); } - @method dispatchData(data: Field) { + @method async dispatchData(data: Field) { this.reducer.dispatch({ isIncrement: Bool(false), otherData: data }); } - @method rollupIncrements() { + @method async rollupIncrements() { // get previous counter & actions hash, assert that they're the same as on-chain values let counter = this.counter.get(); - this.counter.assertEquals(counter); + this.counter.requireEquals(counter); let actionState = this.actionState.get(); - this.actionState.assertEquals(actionState); + this.actionState.requireEquals(actionState); // compute the new counter and hash from pending actions let pendingActions = this.reducer.getActions({ @@ -95,9 +92,9 @@ if (doProofs) { } console.log('deploy'); -let tx = await Mina.transaction(feePayer, () => { +let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); + await zkapp.deploy(); zkapp.counter.set(initialCounter); zkapp.actionState.set(Reducer.initialActionState); }); @@ -107,22 +104,22 @@ console.log('applying actions..'); console.log('action 1'); -tx = await Mina.transaction(feePayer, () => { - zkapp.incrementCounter(); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.incrementCounter(); }); await tx.prove(); await tx.sign([feePayerKey]).send(); console.log('action 2'); -tx = await Mina.transaction(feePayer, () => { - zkapp.incrementCounter(); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.incrementCounter(); }); await tx.prove(); await tx.sign([feePayerKey]).send(); console.log('action 3'); -tx = await Mina.transaction(feePayer, () => { - zkapp.incrementCounter(); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.incrementCounter(); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -131,8 +128,8 @@ console.log('rolling up pending actions..'); console.log('state before: ' + zkapp.counter.get()); -tx = await Mina.transaction(feePayer, () => { - zkapp.rollupIncrements(); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.rollupIncrements(); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -143,15 +140,15 @@ assert.deepEqual(zkapp.counter.get().toString(), '3'); console.log('applying more actions'); console.log('action 4 (no increment)'); -tx = await Mina.transaction(feePayer, () => { - zkapp.dispatchData(Field.random()); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.dispatchData(Field.random()); }); await tx.prove(); await tx.sign([feePayerKey]).send(); console.log('action 5'); -tx = await Mina.transaction(feePayer, () => { - zkapp.incrementCounter(); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.incrementCounter(); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -160,8 +157,8 @@ console.log('rolling up pending actions..'); console.log('state before: ' + zkapp.counter.get()); -tx = await Mina.transaction(feePayer, () => { - zkapp.rollupIncrements(); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.rollupIncrements(); }); await tx.prove(); await tx.sign([feePayerKey]).send(); diff --git a/src/examples/zkapps/reducer/reducer.ts b/src/examples/zkapps/reducer/reducer.ts deleted file mode 100644 index 777af00c71..0000000000 --- a/src/examples/zkapps/reducer/reducer.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { - Field, - state, - State, - method, - PrivateKey, - SmartContract, - Mina, - AccountUpdate, - isReady, - Permissions, - Reducer, -} from 'snarkyjs'; - -await isReady; - -const INCREMENT = Field(1); - -class CounterZkapp extends SmartContract { - // the "reducer" field describes a type of action that we can dispatch, and reduce later - reducer = Reducer({ actionType: Field }); - - // on-chain version of our state. it will typically lag behind the - // version that's implicitly represented by the list of actions - @state(Field) counter = State(); - // helper field to store the point in the action history that our on-chain state is at - @state(Field) actionState = State(); - - @method incrementCounter() { - this.reducer.dispatch(INCREMENT); - } - - @method rollupIncrements() { - // get previous counter & actions hash, assert that they're the same as on-chain values - let counter = this.counter.get(); - this.counter.assertEquals(counter); - let actionState = this.actionState.get(); - this.actionState.assertEquals(actionState); - - // compute the new counter and hash from pending actions - let pendingActions = this.reducer.getActions({ - fromActionState: actionState, - }); - - let { state: newCounter, actionState: newActionState } = - this.reducer.reduce( - pendingActions, - // state type - Field, - // function that says how to apply an action - (state: Field, _action: Field) => { - return state.add(1); - }, - { state: counter, actionState } - ); - - // update on-chain state - this.counter.set(newCounter); - this.actionState.set(newActionState); - } -} - -const doProofs = true; -const initialCounter = Field(0); - -let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); -Mina.setActiveInstance(Local); - -// a test account that pays all the fees, and puts additional funds into the zkapp -let feePayerKey = Local.testAccounts[0].privateKey; -let feePayer = Local.testAccounts[0].publicKey; - -// the zkapp account -let zkappKey = PrivateKey.fromBase58( - 'EKEQc95PPQZnMY9d9p1vq1MWLeDJKtvKj4V75UDG3rjnf32BerWD' -); -let zkappAddress = zkappKey.toPublicKey(); -let zkapp = new CounterZkapp(zkappAddress); -if (doProofs) { - console.log('compile'); - await CounterZkapp.compile(); -} else { - // TODO: if we don't do this, then `analyzeMethods()` will be called during `runUnchecked()` in the tx callback below, - // which currently fails due to `finalize_is_running` in snarky not resetting internal state, and instead setting is_running unconditionally to false, - // so we can't nest different snarky circuit runners - CounterZkapp.analyzeMethods(); -} - -console.log('deploy'); -let tx = await Mina.transaction(feePayer, () => { - AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); - zkapp.counter.set(initialCounter); - zkapp.actionState.set(Reducer.initialActionState); -}); -await tx.sign([feePayerKey, zkappKey]).send(); - -console.log('applying actions..'); - -console.log('action 1'); - -tx = await Mina.transaction(feePayer, () => { - zkapp.incrementCounter(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('action 2'); -tx = await Mina.transaction(feePayer, () => { - zkapp.incrementCounter(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('action 3'); -tx = await Mina.transaction(feePayer, () => { - zkapp.incrementCounter(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('rolling up pending actions..'); - -console.log('state before: ' + zkapp.counter.get()); - -tx = await Mina.transaction(feePayer, () => { - zkapp.rollupIncrements(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('state after rollup: ' + zkapp.counter.get()); - -console.log('applying more actions'); - -console.log('action 4'); -tx = await Mina.transaction(feePayer, () => { - zkapp.incrementCounter(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('action 5'); -tx = await Mina.transaction(feePayer, () => { - zkapp.incrementCounter(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('rolling up pending actions..'); - -console.log('state before: ' + zkapp.counter.get()); - -tx = await Mina.transaction(feePayer, () => { - zkapp.rollupIncrements(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('state after rollup: ' + zkapp.counter.get()); diff --git a/src/examples/zkapps/set_local_preconditions_zkapp.ts b/src/examples/zkapps/set-local-preconditions-zkapp.ts similarity index 78% rename from src/examples/zkapps/set_local_preconditions_zkapp.ts rename to src/examples/zkapps/set-local-preconditions-zkapp.ts index 6d12934c56..ba43b82cdc 100644 --- a/src/examples/zkapps/set_local_preconditions_zkapp.ts +++ b/src/examples/zkapps/set-local-preconditions-zkapp.ts @@ -8,25 +8,19 @@ For example, you only want your smart contract to initiate a pay out when the `b import { method, - UInt64, PrivateKey, SmartContract, Mina, AccountUpdate, - isReady, - Permissions, - DeployArgs, UInt32, -} from 'snarkyjs'; +} from 'o1js'; const doProofs = false; -await isReady; - class SimpleZkapp extends SmartContract { - @method blockheightEquals(y: UInt32) { + @method async blockheightEquals(y: UInt32) { let length = this.network.blockchainLength.get(); - this.network.blockchainLength.assertEquals(length); + this.network.blockchainLength.requireEquals(length); length.assertEquals(y); } @@ -51,18 +45,18 @@ if (doProofs) { } console.log('deploy'); -let tx = await Mina.transaction(feePayer, () => { +let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); + await zkapp.deploy(); }); await tx.sign([feePayerKey, zkappKey]).send(); let blockHeight: UInt32 = UInt32.zero; console.log('assert block height 0'); -tx = await Mina.transaction(feePayer, () => { +tx = await Mina.transaction(feePayer, async () => { // block height starts at 0 - zkapp.blockheightEquals(UInt32.from(blockHeight)); + await zkapp.blockheightEquals(UInt32.from(blockHeight)); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -71,8 +65,8 @@ blockHeight = UInt32.from(500); Local.setBlockchainLength(blockHeight); console.log('assert block height 500'); -tx = await Mina.transaction(feePayer, () => { - zkapp.blockheightEquals(UInt32.from(blockHeight)); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.blockheightEquals(UInt32.from(blockHeight)); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -81,8 +75,8 @@ blockHeight = UInt32.from(300); Local.setBlockchainLength(UInt32.from(5)); console.log('invalid block height precondition'); try { - tx = await Mina.transaction(feePayer, () => { - zkapp.blockheightEquals(UInt32.from(blockHeight)); + tx = await Mina.transaction(feePayer, async () => { + await zkapp.blockheightEquals(UInt32.from(blockHeight)); }); await tx.prove(); await tx.sign([feePayerKey]).send(); diff --git a/src/examples/zkapps/simple_zkapp_payment.ts b/src/examples/zkapps/simple-zkapp-payment.ts similarity index 74% rename from src/examples/zkapps/simple_zkapp_payment.ts rename to src/examples/zkapps/simple-zkapp-payment.ts index 1f3f6cc2d5..90d54fdd6a 100644 --- a/src/examples/zkapps/simple_zkapp_payment.ts +++ b/src/examples/zkapps/simple-zkapp-payment.ts @@ -1,16 +1,12 @@ import { - isReady, method, Mina, AccountUpdate, PrivateKey, SmartContract, UInt64, - shutdown, Permissions, -} from 'snarkyjs'; - -await isReady; +} from 'o1js'; class SendMINAExample extends SmartContract { init() { @@ -21,12 +17,15 @@ class SendMINAExample extends SmartContract { }); } - @method withdraw(amount: UInt64) { - this.send({ to: this.sender, amount }); + @method async withdraw(amount: UInt64) { + // unconstrained because we don't care where the user wants to withdraw to + let to = this.sender.getUnconstrained(); + this.send({ to, amount }); } - @method deposit(amount: UInt64) { - let senderUpdate = AccountUpdate.createSigned(this.sender); + @method async deposit(amount: UInt64) { + let sender = this.sender.getUnconstrained(); // unconstrained because we're already requiring a signature in the next line + let senderUpdate = AccountUpdate.createSigned(sender); senderUpdate.send({ to: this, amount }); } } @@ -67,26 +66,26 @@ let zkapp = new SendMINAExample(zkappAddress); let tx; console.log('deploy and fund user accounts'); -tx = await Mina.transaction(feePayer, () => { +tx = await Mina.transaction(feePayer, async () => { let feePayerUpdate = AccountUpdate.fundNewAccount(feePayer, 3); feePayerUpdate.send({ to: account1Address, amount: 2e9 }); feePayerUpdate.send({ to: account2Address, amount: 0 }); // just touch account #2, so it's created - zkapp.deploy(); + await zkapp.deploy(); }); await tx.sign([feePayerKey, zkappKey]).send(); printBalances(); console.log('---------- deposit MINA into zkApp (with proof) ----------'); -tx = await Mina.transaction(account1Address, () => { - zkapp.deposit(UInt64.from(1e9)); +tx = await Mina.transaction(account1Address, async () => { + await zkapp.deposit(UInt64.from(1e9)); }); await tx.prove(); await tx.sign([account1Key]).send(); printBalances(); console.log('---------- send MINA from zkApp (with proof) ----------'); -tx = await Mina.transaction(account1Address, () => { - zkapp.withdraw(UInt64.from(1e9)); +tx = await Mina.transaction(account1Address, async () => { + await zkapp.withdraw(UInt64.from(1e9)); }); await tx.prove(); await tx.sign([account1Key]).send(); @@ -95,11 +94,9 @@ printBalances(); console.log( '---------- send MINA between accounts (with signature) ----------' ); -tx = await Mina.transaction(account1Address, () => { +tx = await Mina.transaction(account1Address, async () => { let account1Update = AccountUpdate.createSigned(account1Address); account1Update.send({ to: account2Address, amount: 1e9 }); }); await tx.sign([account1Key]).send(); printBalances(); - -shutdown(); diff --git a/src/examples/zkapps/simple_zkapp_with_proof.ts b/src/examples/zkapps/simple-zkapp-with-proof.ts similarity index 80% rename from src/examples/zkapps/simple_zkapp_with_proof.ts rename to src/examples/zkapps/simple-zkapp-with-proof.ts index 599cbaaa8e..aa098a19ce 100644 --- a/src/examples/zkapps/simple_zkapp_with_proof.ts +++ b/src/examples/zkapps/simple-zkapp-with-proof.ts @@ -7,17 +7,14 @@ import { SmartContract, Mina, AccountUpdate, - isReady, ZkappPublicInput, SelfProof, verify, Empty, -} from 'snarkyjs'; - -await isReady; +} from 'o1js'; class TrivialZkapp extends SmartContract { - @method proveSomething(hasToBe1: Field) { + @method async proveSomething(hasToBe1: Field) { hasToBe1.assertEquals(1); } } @@ -26,12 +23,12 @@ class TrivialProof extends TrivialZkapp.Proof() {} class NotSoSimpleZkapp extends SmartContract { @state(Field) x = State(); - @method initialize(proof: TrivialProof) { + @method async initialize(proof: TrivialProof) { proof.verify(); this.x.set(Field(1)); } - @method update( + @method async update( y: Field, oldProof: SelfProof, trivialProof: TrivialProof @@ -39,7 +36,7 @@ class NotSoSimpleZkapp extends SmartContract { oldProof.verify(); trivialProof.verify(); let x = this.x.get(); - this.x.assertEquals(x); + this.x.requireEquals(x); this.x.set(x.add(y)); } } @@ -67,8 +64,8 @@ let { verificationKey: trivialVerificationKey } = await TrivialZkapp.compile(); // would also improve the return type -- `Proof` instead of `(Proof | undefined)[]` console.log('prove (trivial zkapp)'); let [trivialProof] = await ( - await Mina.transaction(feePayer, () => { - new TrivialZkapp(zkappAddress2).proveSomething(Field(1)); + await Mina.transaction(feePayer, async () => { + await new TrivialZkapp(zkappAddress2).proveSomething(Field(1)); }) ).prove(); @@ -84,16 +81,16 @@ let { verificationKey } = await NotSoSimpleZkapp.compile(); let zkapp = new NotSoSimpleZkapp(zkappAddress); console.log('deploy'); -let tx = await Mina.transaction(feePayer, () => { +let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy({ zkappKey }); + await zkapp.deploy(); }); await tx.prove(); -await tx.sign([feePayerKey]).send(); +await tx.sign([feePayerKey, zkappKey]).send(); console.log('initialize'); -tx = await Mina.transaction(feePayerKey, () => { - zkapp.initialize(trivialProof!); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.initialize(trivialProof!); }); let [proof] = await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -107,8 +104,8 @@ proof = await testJsonRoundtripAndVerify( console.log('initial state: ' + zkapp.x.get()); console.log('update'); -tx = await Mina.transaction(feePayer, () => { - zkapp.update(Field(3), proof!, trivialProof!); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.update(Field(3), proof!, trivialProof!); }); [proof] = await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -122,8 +119,8 @@ proof = await testJsonRoundtripAndVerify( console.log('state 2: ' + zkapp.x.get()); console.log('update'); -tx = await Mina.transaction(feePayer, () => { - zkapp.update(Field(3), proof!, trivialProof!); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.update(Field(3), proof!, trivialProof!); }); [proof] = await tx.prove(); await tx.sign([feePayerKey]).send(); diff --git a/src/examples/sudoku/index.ts b/src/examples/zkapps/sudoku/index.ts similarity index 81% rename from src/examples/sudoku/index.ts rename to src/examples/zkapps/sudoku/index.ts index 8bcfdf0a50..859762fa63 100644 --- a/src/examples/sudoku/index.ts +++ b/src/examples/zkapps/sudoku/index.ts @@ -1,6 +1,6 @@ import { Sudoku, SudokuZkApp } from './sudoku.js'; import { cloneSudoku, generateSudoku, solveSudoku } from './sudoku-lib.js'; -import { AccountUpdate, Mina, PrivateKey, shutdown } from 'snarkyjs'; +import { AccountUpdate, Mina, PrivateKey } from 'o1js'; // setup const Local = Mina.LocalBlockchain(); @@ -13,7 +13,7 @@ const zkAppAddress = zkAppPrivateKey.toPublicKey(); // create an instance of the smart contract const zkApp = new SudokuZkApp(zkAppAddress); -let methods = SudokuZkApp.analyzeMethods(); +let methods = await SudokuZkApp.analyzeMethods(); console.log( 'first 5 gates of submitSolution method:', ...methods.submitSolution.gates.slice(0, 5) @@ -21,10 +21,10 @@ console.log( console.log('Deploying and initializing Sudoku...'); await SudokuZkApp.compile(); -let tx = await Mina.transaction(account, () => { +let tx = await Mina.transaction(account, async () => { AccountUpdate.fundNewAccount(account); - zkApp.deploy(); - zkApp.update(Sudoku.from(sudoku)); + await zkApp.deploy(); + await zkApp.update(Sudoku.from(sudoku)); }); await tx.prove(); /** @@ -47,8 +47,8 @@ noSolution[0][0] = (noSolution[0][0] % 9) + 1; console.log('Submitting wrong solution...'); try { - let tx = await Mina.transaction(account, () => { - zkApp.submitSolution(Sudoku.from(sudoku), Sudoku.from(noSolution)); + let tx = await Mina.transaction(account, async () => { + await zkApp.submitSolution(Sudoku.from(sudoku), Sudoku.from(noSolution)); }); await tx.prove(); await tx.sign([accountKey]).send(); @@ -59,12 +59,9 @@ console.log('Is the sudoku solved?', zkApp.isSolved.get().toBoolean()); // submit the actual solution console.log('Submitting solution...'); -tx = await Mina.transaction(account, () => { - zkApp.submitSolution(Sudoku.from(sudoku), Sudoku.from(solution!)); +tx = await Mina.transaction(account, async () => { + await zkApp.submitSolution(Sudoku.from(sudoku), Sudoku.from(solution!)); }); await tx.prove(); await tx.sign([accountKey]).send(); console.log('Is the sudoku solved?', zkApp.isSolved.get().toBoolean()); - -// cleanup -await shutdown(); diff --git a/src/examples/sudoku/sudoku-lib.js b/src/examples/zkapps/sudoku/sudoku-lib.js similarity index 100% rename from src/examples/sudoku/sudoku-lib.js rename to src/examples/zkapps/sudoku/sudoku-lib.js diff --git a/src/examples/sudoku/sudoku.ts b/src/examples/zkapps/sudoku/sudoku.ts similarity index 88% rename from src/examples/sudoku/sudoku.ts rename to src/examples/zkapps/sudoku/sudoku.ts index 1367198abb..5f17d9ce3a 100644 --- a/src/examples/sudoku/sudoku.ts +++ b/src/examples/zkapps/sudoku/sudoku.ts @@ -5,16 +5,13 @@ import { Bool, state, State, - isReady, Poseidon, Struct, - Circuit, -} from 'snarkyjs'; + Provable, +} from 'o1js'; export { Sudoku, SudokuZkApp }; -await isReady; - class Sudoku extends Struct({ value: Provable.Array(Provable.Array(Field, 9), 9), }) { @@ -23,7 +20,7 @@ class Sudoku extends Struct({ } hash() { - return Poseidon.hash(this.value.flat()); + return Poseidon.hash(Sudoku.toFields(this)); } } @@ -37,16 +34,19 @@ class SudokuZkApp extends SmartContract { * to ensure the entire state is overwritten. * however, it's good to have an example which tests the CLI's ability to handle init() decorated with `@method`. */ - @method init() { + @method async init() { super.init(); } - @method update(sudokuInstance: Sudoku) { + @method async update(sudokuInstance: Sudoku) { this.sudokuHash.set(sudokuInstance.hash()); this.isSolved.set(Bool(false)); } - @method submitSolution(sudokuInstance: Sudoku, solutionInstance: Sudoku) { + @method async submitSolution( + sudokuInstance: Sudoku, + solutionInstance: Sudoku + ) { let sudoku = sudokuInstance.value; let solution = solutionInstance.value; @@ -98,7 +98,7 @@ class SudokuZkApp extends SmartContract { // finally, we check that the sudoku is the one that was originally deployed let sudokuHash = this.sudokuHash.get(); // get the hash from the blockchain - this.sudokuHash.assertEquals(sudokuHash); // precondition that links this.sudokuHash.get() to the actual on-chain state + this.sudokuHash.requireEquals(sudokuHash); // precondition that links this.sudokuHash.get() to the actual on-chain state sudokuInstance .hash() .assertEquals(sudokuHash, 'sudoku matches the one committed on-chain'); diff --git a/src/examples/zkapps/tictoc.ts b/src/examples/zkapps/tictoc.ts deleted file mode 100644 index 35bd2de501..0000000000 --- a/src/examples/zkapps/tictoc.ts +++ /dev/null @@ -1,17 +0,0 @@ -// helper for printing timings - -export { tic, toc }; - -let timingStack: [string, number][] = []; -let i = 0; - -function tic(label = `Run command ${i++}`) { - process.stdout.write(`${label}... `); - timingStack.push([label, Date.now()]); -} - -function toc() { - let [label, start] = timingStack.pop()!; - let time = (Date.now() - start) / 1000; - process.stdout.write(`\r${label}... ${time.toFixed(3)} sec\n`); -} diff --git a/src/examples/zkapps/token-with-proofs.ts b/src/examples/zkapps/token-with-proofs.ts new file mode 100644 index 0000000000..37e0a3437f --- /dev/null +++ b/src/examples/zkapps/token-with-proofs.ts @@ -0,0 +1,141 @@ +import { + method, + Mina, + AccountUpdate, + PrivateKey, + SmartContract, + PublicKey, + TokenId, + TokenContract, + AccountUpdateForest, +} from 'o1js'; + +class Token extends TokenContract { + @method + async approveBase(forest: AccountUpdateForest) { + this.checkZeroBalanceChange(forest); + } + + @method async mint(receiverAddress: PublicKey) { + let amount = 1_000_000; + this.internal.mint({ address: receiverAddress, amount }); + } + + @method async burn(receiverAddress: PublicKey) { + let amount = 1_000; + this.internal.burn({ address: receiverAddress, amount }); + } +} + +class ZkAppB extends SmartContract { + @method async approveSend() { + this.balance.subInPlace(1_000); + } +} + +class ZkAppC extends SmartContract { + @method async approveSend() { + this.balance.subInPlace(1_000); + } +} + +let Local = Mina.LocalBlockchain(); +Mina.setActiveInstance(Local); + +let [ + { publicKey: sender, privateKey: senderKey }, + { publicKey: tokenAccount1 }, +] = Local.testAccounts; +let initialBalance = 10_000_000; + +let tokenZkAppKey = PrivateKey.random(); +let tokenZkAppAddress = tokenZkAppKey.toPublicKey(); + +let zkAppCKey = PrivateKey.random(); +let zkAppCAddress = zkAppCKey.toPublicKey(); + +let zkAppBKey = PrivateKey.random(); +let zkAppBAddress = zkAppBKey.toPublicKey(); + +let tokenZkApp = new Token(tokenZkAppAddress); +let tokenId = tokenZkApp.deriveTokenId(); + +let zkAppB = new ZkAppB(zkAppBAddress, tokenId); +let zkAppC = new ZkAppC(zkAppCAddress, tokenId); +let tx; + +console.log('tokenZkAppAddress', tokenZkAppAddress.toBase58()); +console.log('zkAppB', zkAppBAddress.toBase58()); +console.log('zkAppC', zkAppCAddress.toBase58()); +console.log('receiverAddress', tokenAccount1.toBase58()); +console.log('feePayer', sender.toBase58()); +console.log('-------------------------------------------'); + +console.log('compile (TokenContract)'); +await Token.compile(); +console.log('compile (ZkAppB)'); +await ZkAppB.compile(); +console.log('compile (ZkAppC)'); +await ZkAppC.compile(); + +console.log('deploy tokenZkApp'); +tx = await Mina.transaction(sender, async () => { + await tokenZkApp.deploy(); + AccountUpdate.fundNewAccount(sender).send({ + to: tokenZkApp.self, + amount: initialBalance, + }); +}); +await tx.sign([senderKey, tokenZkAppKey]).send(); + +console.log('deploy zkAppB and zkAppC'); +tx = await Mina.transaction(sender, async () => { + AccountUpdate.fundNewAccount(sender, 2); + await zkAppC.deploy(); + await zkAppB.deploy(); + await tokenZkApp.approveAccountUpdates([zkAppC.self, zkAppB.self]); +}); +console.log('deploy zkAppB and zkAppC (proof)'); +await tx.prove(); +await tx.sign([senderKey, zkAppBKey, zkAppCKey]).send(); + +console.log('mint token to zkAppB'); +tx = await Mina.transaction(sender, async () => { + await tokenZkApp.mint(zkAppBAddress); +}); +await tx.prove(); +await tx.sign([senderKey]).send(); + +console.log('approve send from zkAppB'); +tx = await Mina.transaction(sender, async () => { + await zkAppB.approveSend(); + + // we call the token contract with the self update + await tokenZkApp.transfer(zkAppB.self, zkAppCAddress, 1_000); +}); +console.log('approve send (proof)'); +await tx.prove(); +await tx.sign([senderKey]).send(); + +console.log( + `zkAppC's balance for tokenId: ${TokenId.toBase58(tokenId)}`, + Mina.getBalance(zkAppCAddress, tokenId).value.toBigInt() +); + +console.log('approve send from zkAppC'); +tx = await Mina.transaction(sender, async () => { + // Pay for tokenAccount1's account creation + AccountUpdate.fundNewAccount(sender); + await zkAppC.approveSend(); + + // we call the token contract with the tree + await tokenZkApp.transfer(zkAppC.self, tokenAccount1, 1_000); +}); +console.log('approve send (proof)'); +await tx.prove(); +await tx.sign([senderKey]).send(); + +console.log( + `tokenAccount1's balance for tokenId: ${TokenId.toBase58(tokenId)}`, + Mina.getBalance(tokenAccount1, tokenId).value.toBigInt() +); diff --git a/src/examples/zkapps/token_with_proofs.ts b/src/examples/zkapps/token_with_proofs.ts deleted file mode 100644 index 7cdcdd96d0..0000000000 --- a/src/examples/zkapps/token_with_proofs.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { - isReady, - method, - Mina, - AccountUpdate, - PrivateKey, - SmartContract, - PublicKey, - UInt64, - shutdown, - Int64, - Experimental, - Permissions, - DeployArgs, - VerificationKey, - TokenId, -} from 'snarkyjs'; - -await isReady; - -class TokenContract extends SmartContract { - deploy(args: DeployArgs) { - super.deploy(args); - this.setPermissions({ - ...Permissions.default(), - access: Permissions.proofOrSignature(), - }); - this.balance.addInPlace(UInt64.from(initialBalance)); - } - - @method tokenDeploy(deployer: PrivateKey, verificationKey: VerificationKey) { - let address = deployer.toPublicKey(); - let tokenId = this.token.id; - let deployUpdate = Experimental.createChildAccountUpdate( - this.self, - address, - tokenId - ); - deployUpdate.account.permissions.set(Permissions.default()); - deployUpdate.account.verificationKey.set(verificationKey); - deployUpdate.sign(deployer); - } - - @method mint(receiverAddress: PublicKey) { - let amount = UInt64.from(1_000_000); - this.token.mint({ address: receiverAddress, amount }); - } - - @method burn(receiverAddress: PublicKey) { - let amount = UInt64.from(1_000); - this.token.burn({ address: receiverAddress, amount }); - } - - @method sendTokens( - senderAddress: PublicKey, - receiverAddress: PublicKey, - callback: Experimental.Callback - ) { - let senderAccountUpdate = this.approve( - callback, - AccountUpdate.Layout.AnyChildren - ); - let amount = UInt64.from(1_000); - let negativeAmount = Int64.fromObject( - senderAccountUpdate.body.balanceChange - ); - negativeAmount.assertEquals(Int64.from(amount).neg()); - let tokenId = this.token.id; - senderAccountUpdate.body.tokenId.assertEquals(tokenId); - senderAccountUpdate.body.publicKey.assertEquals(senderAddress); - let receiverAccountUpdate = Experimental.createChildAccountUpdate( - this.self, - receiverAddress, - tokenId - ); - receiverAccountUpdate.balance.addInPlace(amount); - } -} - -class ZkAppB extends SmartContract { - @method approveSend() { - let amount = UInt64.from(1_000); - this.balance.subInPlace(amount); - } -} - -class ZkAppC extends SmartContract { - @method approveSend() { - let amount = UInt64.from(1_000); - this.balance.subInPlace(amount); - } -} - -let Local = Mina.LocalBlockchain(); -Mina.setActiveInstance(Local); - -let feePayer = Local.testAccounts[0].privateKey; -let initialBalance = 10_000_000; - -let tokenZkAppKey = PrivateKey.random(); -let tokenZkAppAddress = tokenZkAppKey.toPublicKey(); - -let zkAppCKey = PrivateKey.random(); -let zkAppCAddress = zkAppCKey.toPublicKey(); - -let zkAppBKey = PrivateKey.random(); -let zkAppBAddress = zkAppBKey.toPublicKey(); - -let tokenAccount1Key = Local.testAccounts[1].privateKey; -let tokenAccount1 = tokenAccount1Key.toPublicKey(); - -let tokenZkApp = new TokenContract(tokenZkAppAddress); -let tokenId = tokenZkApp.token.id; - -let zkAppB = new ZkAppB(zkAppBAddress, tokenId); -let zkAppC = new ZkAppC(zkAppCAddress, tokenId); -let tx; - -console.log('tokenZkAppAddress', tokenZkAppAddress.toBase58()); -console.log('zkAppB', zkAppBAddress.toBase58()); -console.log('zkAppC', zkAppCAddress.toBase58()); -console.log('receiverAddress', tokenAccount1.toBase58()); -console.log('feePayer', feePayer.toPublicKey().toBase58()); -console.log('-------------------------------------------'); - -console.log('compile (TokenContract)'); -await TokenContract.compile(); -console.log('compile (ZkAppB)'); -await ZkAppB.compile(); -console.log('compile (ZkAppC)'); -await ZkAppC.compile(); - -console.log('deploy tokenZkApp'); -tx = await Local.transaction(feePayer, () => { - AccountUpdate.fundNewAccount(feePayer, { initialBalance }); - tokenZkApp.deploy({ zkappKey: tokenZkAppKey }); -}); -await tx.send(); - -console.log('deploy zkAppB'); -tx = await Local.transaction(feePayer, () => { - AccountUpdate.fundNewAccount(feePayer); - tokenZkApp.tokenDeploy(zkAppBKey, ZkAppB._verificationKey!); -}); -console.log('deploy zkAppB (proof)'); -await tx.prove(); -await tx.send(); - -console.log('deploy zkAppC'); -tx = await Local.transaction(feePayer, () => { - AccountUpdate.fundNewAccount(feePayer); - tokenZkApp.tokenDeploy(zkAppCKey, ZkAppC._verificationKey!); -}); -console.log('deploy zkAppC (proof)'); -await tx.prove(); -await tx.send(); - -console.log('mint token to zkAppB'); -tx = await Local.transaction(feePayer, () => { - tokenZkApp.mint(zkAppBAddress); -}); -await tx.prove(); -await tx.send(); - -console.log('approve send from zkAppB'); -tx = await Local.transaction(feePayer, () => { - let approveSendingCallback = Experimental.Callback.create( - zkAppB, - 'approveSend', - [] - ); - // we call the token contract with the callback - tokenZkApp.sendTokens(zkAppBAddress, zkAppCAddress, approveSendingCallback); -}); -console.log('approve send (proof)'); -await tx.prove(); -await tx.send(); - -console.log( - `zkAppC's balance for tokenId: ${TokenId.toBase58(tokenId)}`, - Mina.getBalance(zkAppCAddress, tokenId).value.toBigInt() -); - -console.log('approve send from zkAppC'); -tx = await Local.transaction(feePayer, () => { - // Pay for tokenAccount1's account creation - AccountUpdate.fundNewAccount(feePayer); - let approveSendingCallback = Experimental.Callback.create( - zkAppC, - 'approveSend', - [] - ); - // we call the token contract with the callback - tokenZkApp.sendTokens(zkAppCAddress, tokenAccount1, approveSendingCallback); -}); -console.log('approve send (proof)'); -await tx.prove(); -await tx.send(); - -console.log( - `tokenAccount1's balance for tokenId: ${TokenId.toBase58(tokenId)}`, - Mina.getBalance(tokenAccount1, tokenId).value.toBigInt() -); - -shutdown(); diff --git a/src/examples/zkapps/voting/demo.ts b/src/examples/zkapps/voting/demo.ts index 9b743e4676..1d2a77248d 100644 --- a/src/examples/zkapps/voting/demo.ts +++ b/src/examples/zkapps/voting/demo.ts @@ -1,18 +1,10 @@ // used to do a dry run, without tests // ./run ./src/examples/zkapps/voting/demo.ts -import { - Field, - Mina, - AccountUpdate, - PrivateKey, - UInt64, - Reducer, - Bool, -} from 'snarkyjs'; +import { Mina, AccountUpdate, PrivateKey, UInt64, Reducer, Bool } from 'o1js'; import { VotingApp, VotingAppParams } from './factory.js'; import { Member, MyMerkleWitness } from './member.js'; -import { OffchainStorage } from './off_chain_storage.js'; +import { OffchainStorage } from './off-chain-storage.js'; import { ParticipantPreconditions, ElectionPreconditions, @@ -66,28 +58,28 @@ let candidateStore = new OffchainStorage(3); let votesStore = new OffchainStorage(3); let initialRoot = voterStore.getRoot(); -tx = await Mina.transaction(feePayer, () => { +tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer, 3); - contracts.voting.deploy({ zkappKey: votingKey }); + await contracts.voting.deploy(); contracts.voting.committedVotes.set(votesStore.getRoot()); contracts.voting.accumulatedVotes.set(Reducer.initialActionState); - contracts.candidateContract.deploy({ zkappKey: candidateKey }); + await contracts.candidateContract.deploy(); contracts.candidateContract.committedMembers.set(candidateStore.getRoot()); contracts.candidateContract.accumulatedMembers.set( Reducer.initialActionState ); - contracts.voterContract.deploy({ zkappKey: voterKey }); + await contracts.voterContract.deploy(); contracts.voterContract.committedMembers.set(voterStore.getRoot()); contracts.voterContract.accumulatedMembers.set(Reducer.initialActionState); }); -await tx.sign([feePayerKey]).send(); +await tx.sign([feePayerKey, votingKey, candidateKey, voterKey]).send(); let m: Member = Member.empty(); // lets register three voters -tx = await Mina.transaction(feePayer, () => { +tx = await Mina.transaction(feePayer, async () => { // creating and registering a new voter m = registerMember( /* @@ -101,13 +93,12 @@ tx = await Mina.transaction(feePayer, () => { ); contracts.voting.voterRegistration(m); - if (!params.doProofs) contracts.voting.sign(votingKey); }); await tx.prove(); await tx.sign([feePayerKey]).send(); // lets register three voters -tx = await Mina.transaction(feePayer, () => { +tx = await Mina.transaction(feePayer, async () => { // creating and registering a new voter m = registerMember( /* @@ -121,14 +112,12 @@ tx = await Mina.transaction(feePayer, () => { ); contracts.voting.voterRegistration(m); - - if (!params.doProofs) contracts.voting.sign(votingKey); }); await tx.prove(); await tx.sign([feePayerKey]).send(); // lets register three voters -tx = await Mina.transaction(feePayer, () => { +tx = await Mina.transaction(feePayer, async () => { // creating and registering a new voter m = registerMember( /* @@ -142,8 +131,6 @@ tx = await Mina.transaction(feePayer, () => { ); contracts.voting.voterRegistration(m); - - if (!params.doProofs) contracts.voting.sign(votingKey); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -163,7 +150,7 @@ console.log( Lets register two candidates */ -tx = await Mina.transaction(feePayer, () => { +tx = await Mina.transaction(feePayer, async () => { // creating and registering 1 new candidate let m = registerMember( /* @@ -177,13 +164,12 @@ tx = await Mina.transaction(feePayer, () => { ); contracts.voting.candidateRegistration(m); - if (!params.doProofs) contracts.voting.sign(votingKey); }); await tx.prove(); await tx.sign([feePayerKey]).send(); -tx = await Mina.transaction(feePayer, () => { +tx = await Mina.transaction(feePayer, async () => { // creating and registering 1 new candidate let m = registerMember( /* @@ -197,7 +183,6 @@ tx = await Mina.transaction(feePayer, () => { ); contracts.voting.candidateRegistration(m); - if (!params.doProofs) contracts.voting.sign(votingKey); }); await tx.prove(); @@ -238,9 +223,8 @@ console.log( both the on-chain committedMembers variable and the off-chain merkle tree root need to be equal */ -tx = await Mina.transaction(feePayer, () => { +tx = await Mina.transaction(feePayer, async () => { contracts.voting.approveRegistrations(); - if (!params.doProofs) contracts.voting.sign(votingKey); }); await tx.prove(); @@ -270,13 +254,12 @@ console.log( */ // we have to up the slot so we are within our election period Local.incrementGlobalSlot(5); -tx = await Mina.transaction(feePayer, () => { +tx = await Mina.transaction(feePayer, async () => { let c = candidateStore.get(0n)!; c.witness = new MyMerkleWitness(candidateStore.getWitness(0n)); c.votesWitness = new MyMerkleWitness(votesStore.getWitness(0n)); // we are voting for candidate c, 0n, with voter 2n contracts.voting.vote(c, voterStore.get(2n)!); - if (!params.doProofs) contracts.voting.sign(votingKey); }); await tx.prove(); @@ -294,9 +277,8 @@ console.log( /* counting the votes */ -tx = await Mina.transaction(feePayer, () => { +tx = await Mina.transaction(feePayer, async () => { contracts.voting.countVotes(); - if (!params.doProofs) contracts.voting.sign(votingKey); }); await tx.prove(); diff --git a/src/examples/zkapps/voting/deployContracts.ts b/src/examples/zkapps/voting/deploy-contracts.ts similarity index 86% rename from src/examples/zkapps/voting/deployContracts.ts rename to src/examples/zkapps/voting/deploy-contracts.ts index e9db1ba2b2..e14546ba50 100644 --- a/src/examples/zkapps/voting/deployContracts.ts +++ b/src/examples/zkapps/voting/deploy-contracts.ts @@ -7,7 +7,7 @@ import { PrivateKey, SmartContract, Reducer, -} from 'snarkyjs'; +} from 'o1js'; import { VotingAppParams } from './factory.js'; import { Membership_ } from './membership.js'; @@ -15,8 +15,8 @@ import { Membership_ } from './membership.js'; import { Voting_ } from './voting.js'; class InvalidContract extends SmartContract { - deploy(args: DeployArgs) { - super.deploy(args); + async deploy() { + await super.deploy(); this.account.permissions.set({ ...Permissions.default(), editState: Permissions.none(), @@ -63,22 +63,24 @@ export async function deployContracts( let { voterContract, candidateContract, voting } = contracts; console.log('deploying set of 3 contracts'); - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer, 3); - voting.deploy({ zkappKey: params.votingKey }); + await voting.deploy(); voting.committedVotes.set(votesRoot); voting.accumulatedVotes.set(Reducer.initialActionState); - candidateContract.deploy({ zkappKey: params.candidateKey }); + await candidateContract.deploy(); candidateContract.committedMembers.set(candidateRoot); candidateContract.accumulatedMembers.set(Reducer.initialActionState); - voterContract.deploy({ zkappKey: params.voterKey }); + await voterContract.deploy(); voterContract.committedMembers.set(voterRoot); voterContract.accumulatedMembers.set(Reducer.initialActionState); }); - await tx.sign([feePayerKey]).send(); + await tx + .sign([feePayerKey, params.votingKey, params.candidateKey, params.voterKey]) + .send(); console.log('successfully deployed contracts'); return { @@ -127,10 +129,10 @@ export async function deployInvalidContracts( let { voterContract, candidateContract, voting } = contracts; console.log('deploying set of 3 contracts'); - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer, 3); - voting.deploy({ zkappKey: params.votingKey }); + await voting.deploy(); voting.committedVotes.set(votesRoot); voting.accumulatedVotes.set(Reducer.initialActionState); @@ -140,7 +142,7 @@ export async function deployInvalidContracts( params.candidateKey.toPublicKey() ); - invalidCandidateContract.deploy({ zkappKey: params.candidateKey }); + await invalidCandidateContract.deploy(); candidateContract = invalidCandidateContract as Membership_; @@ -148,11 +150,13 @@ export async function deployInvalidContracts( params.voterKey.toPublicKey() ); - invalidVoterContract.deploy({ zkappKey: params.voterKey }); + await invalidVoterContract.deploy(); voterContract = invalidVoterContract as Membership_; }); - await tx.sign([feePayerKey]).send(); + await tx + .sign([feePayerKey, params.votingKey, params.candidateKey, params.voterKey]) + .send(); console.log('successfully deployed contracts'); return { diff --git a/src/examples/zkapps/voting/dummyContract.ts b/src/examples/zkapps/voting/dummy-contract.ts similarity index 85% rename from src/examples/zkapps/voting/dummyContract.ts rename to src/examples/zkapps/voting/dummy-contract.ts index bc543d85a0..006cacc15a 100644 --- a/src/examples/zkapps/voting/dummyContract.ts +++ b/src/examples/zkapps/voting/dummy-contract.ts @@ -7,13 +7,13 @@ import { DeployArgs, Permissions, TransactionVersion, -} from 'snarkyjs'; +} from 'o1js'; export class DummyContract extends SmartContract { @state(Field) sum = State(); - deploy(args: DeployArgs) { - super.deploy(args); + async deploy(args: DeployArgs) { + await super.deploy(args); this.account.permissions.set({ ...Permissions.default(), editState: Permissions.proofOrSignature(), @@ -31,7 +31,7 @@ export class DummyContract extends SmartContract { /** * Method used to add two variables together. */ - @method add(x: Field, y: Field) { + @method async add(x: Field, y: Field) { this.sum.set(x.add(y)); } } diff --git a/src/examples/zkapps/voting/election-preconditions.ts b/src/examples/zkapps/voting/election-preconditions.ts new file mode 100644 index 0000000000..131fc69012 --- /dev/null +++ b/src/examples/zkapps/voting/election-preconditions.ts @@ -0,0 +1,6 @@ +import { Struct, UInt32 } from 'o1js'; + +export default class ElectionPreconditions extends Struct({ + startElection: UInt32, + endElection: UInt32, +}) {} diff --git a/src/examples/zkapps/voting/election_preconditions.ts b/src/examples/zkapps/voting/election_preconditions.ts deleted file mode 100644 index 4e8c66a982..0000000000 --- a/src/examples/zkapps/voting/election_preconditions.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { CircuitValue, prop, UInt32 } from 'snarkyjs'; - -export default class ElectionPreconditions extends CircuitValue { - @prop startElection: UInt32; - @prop endElection: UInt32; - - constructor(startElection: UInt32, endElection: UInt32) { - super(); - this.startElection = startElection; - this.endElection = endElection; - } -} diff --git a/src/examples/zkapps/voting/factory.ts b/src/examples/zkapps/voting/factory.ts index 84fb0783f6..21156d2bf9 100644 --- a/src/examples/zkapps/voting/factory.ts +++ b/src/examples/zkapps/voting/factory.ts @@ -3,7 +3,7 @@ * Requires a set of preconditions. */ -import { PrivateKey } from 'snarkyjs'; +import { PrivateKey } from 'o1js'; import { Membership, Membership_ } from './membership.js'; import { ElectionPreconditions, @@ -11,7 +11,9 @@ import { } from './preconditions.js'; import { Voting, Voting_ } from './voting.js'; -export interface VotingAppParams { +export { VotingAppParams }; + +type VotingAppParams = { candidatePreconditions: ParticipantPreconditions; voterPreconditions: ParticipantPreconditions; electionPreconditions: ElectionPreconditions; @@ -19,7 +21,7 @@ export interface VotingAppParams { candidateKey: PrivateKey; votingKey: PrivateKey; doProofs: boolean; -} +}; function defaultParams(): VotingAppParams { return { diff --git a/src/examples/zkapps/voting/member.ts b/src/examples/zkapps/voting/member.ts index ad2dc7c63b..fe9d189128 100644 --- a/src/examples/zkapps/voting/member.ts +++ b/src/examples/zkapps/voting/member.ts @@ -1,13 +1,11 @@ import { - Bool, - CircuitValue, Field, - prop, PublicKey, UInt64, Poseidon, MerkleWitness, -} from 'snarkyjs'; + Struct, +} from 'o1js'; export class MyMerkleWitness extends MerkleWitness(3) {} let w = { @@ -18,24 +16,24 @@ let dummyWitness = Array.from(Array(MyMerkleWitness.height - 1).keys()).map( () => w ); -export class Member extends CircuitValue { - @prop publicKey: PublicKey; - @prop balance: UInt64; +export class Member extends Struct({ + publicKey: PublicKey, + balance: UInt64, - // will need this to keep track of votes for candidates - @prop votes: Field; - - @prop witness: MyMerkleWitness; - @prop votesWitness: MyMerkleWitness; + // need this to keep track of votes for candidates + votes: Field, + witness: MyMerkleWitness, + votesWitness: MyMerkleWitness, +}) { constructor(publicKey: PublicKey, balance: UInt64) { - super(); - this.publicKey = publicKey; - this.balance = balance; - this.votes = Field(0); - - this.witness = new MyMerkleWitness(dummyWitness); - this.votesWitness = new MyMerkleWitness(dummyWitness); + super({ + publicKey, + balance, + votes: Field(0), + witness: new MyMerkleWitness(dummyWitness), + votesWitness: new MyMerkleWitness(dummyWitness), + }); } getHash(): Field { @@ -52,8 +50,8 @@ export class Member extends CircuitValue { return this; } - static empty() { - return new Member(PublicKey.empty(), UInt64.zero); + static empty any>(): InstanceType { + return new Member(PublicKey.empty(), UInt64.zero) as any; } static from(publicKey: PublicKey, balance: UInt64) { diff --git a/src/examples/zkapps/voting/membership.ts b/src/examples/zkapps/voting/membership.ts index 42e1609c0b..2c18a1ad80 100644 --- a/src/examples/zkapps/voting/membership.ts +++ b/src/examples/zkapps/voting/membership.ts @@ -4,7 +4,6 @@ import { state, State, method, - DeployArgs, Permissions, Bool, PublicKey, @@ -13,17 +12,19 @@ import { AccountUpdate, Provable, TransactionVersion, -} from 'snarkyjs'; +} from 'o1js'; import { Member } from './member.js'; import { ParticipantPreconditions } from './preconditions.js'; let participantPreconditions = ParticipantPreconditions.default; -interface MembershipParams { +Provable; + +type MembershipParams = { participantPreconditions: ParticipantPreconditions; contractAddress: PublicKey; doProofs: boolean; -} +}; /** * Returns a new contract instance that based on a set of preconditions. @@ -63,13 +64,13 @@ export class Membership_ extends SmartContract { events = { newMemberState: provablePure({ - committedMembersRoot: Field, accumulatedMembersRoot: Field, + committedMembersRoot: Field, }), }; - deploy(args: DeployArgs) { - super.deploy(args); + async deploy() { + await super.deploy(); this.account.permissions.set({ ...Permissions.default(), editState: Permissions.proofOrSignature(), @@ -88,7 +89,8 @@ export class Membership_ extends SmartContract { * Dispatches a new member sequence event. * @param member */ - @method addEntry(member: Member): Bool { + @method.returns(Bool) + async addEntry(member: Member) { // Emit event that indicates adding this item // Preconditions: Restrict who can vote or who can be a candidate @@ -98,7 +100,7 @@ export class Membership_ extends SmartContract { let accountUpdate = AccountUpdate.create(member.publicKey); - accountUpdate.account.balance.assertEquals( + accountUpdate.account.balance.requireEquals( accountUpdate.account.balance.get() ); @@ -114,7 +116,7 @@ export class Membership_ extends SmartContract { ); let accumulatedMembers = this.accumulatedMembers.get(); - this.accumulatedMembers.assertEquals(accumulatedMembers); + this.accumulatedMembers.requireEquals(accumulatedMembers); // checking if the member already exists within the accumulator let { state: exists } = this.reducer.reduce( @@ -123,7 +125,7 @@ export class Membership_ extends SmartContract { }), Bool, (state: Bool, action: Member) => { - return action.equals(member).or(state); + return Provable.equal(Member, action, member).or(state); }, // initial state { state: Bool(false), actionState: accumulatedMembers } @@ -147,29 +149,30 @@ export class Membership_ extends SmartContract { * @param accountId * @returns true if member exists */ - @method isMember(member: Member): Bool { + @method.returns(Bool) + async isMember(member: Member) { // Verify membership (voter or candidate) with the accountId via merkle tree committed to by the sequence events and returns a boolean // Preconditions: Item exists in committed storage let committedMembers = this.committedMembers.get(); - this.committedMembers.assertEquals(committedMembers); + this.committedMembers.requireEquals(committedMembers); return member.witness - .calculateRootSlow(member.getHash()) + .calculateRoot(member.getHash()) .equals(committedMembers); } /** * Method used to commit to the accumulated list of members. */ - @method publish() { + @method async publish() { // Commit to the items accumulated so far. This is a periodic update let accumulatedMembers = this.accumulatedMembers.get(); - this.accumulatedMembers.assertEquals(accumulatedMembers); + this.accumulatedMembers.requireEquals(accumulatedMembers); let committedMembers = this.committedMembers.get(); - this.committedMembers.assertEquals(committedMembers); + this.committedMembers.requireEquals(committedMembers); let pendingActions = this.reducer.getActions({ fromActionState: accumulatedMembers, @@ -191,7 +194,7 @@ export class Membership_ extends SmartContract { // otherwise, we simply return the unmodified state - this is our way of branching return Provable.if( isRealMember, - action.witness.calculateRootSlow(action.getHash()), + action.witness.calculateRoot(action.getHash()), state ); }, diff --git a/src/examples/zkapps/voting/off_chain_storage.ts b/src/examples/zkapps/voting/off-chain-storage.ts similarity index 93% rename from src/examples/zkapps/voting/off_chain_storage.ts rename to src/examples/zkapps/voting/off-chain-storage.ts index e0d5ac40e0..2d30258ee3 100644 --- a/src/examples/zkapps/voting/off_chain_storage.ts +++ b/src/examples/zkapps/voting/off-chain-storage.ts @@ -1,6 +1,6 @@ // Merkle Tree and off chain storage -import { Field, MerkleTree } from 'snarkyjs'; +import { Field, MerkleTree } from 'o1js'; export { OffchainStorage }; diff --git a/src/examples/zkapps/voting/participant_preconditions.ts b/src/examples/zkapps/voting/participant_preconditions.ts deleted file mode 100644 index d580d60b38..0000000000 --- a/src/examples/zkapps/voting/participant_preconditions.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CircuitValue, prop, UInt64 } from 'snarkyjs'; - -export default class ParticipantPreconditions extends CircuitValue { - @prop minMinaVote: UInt64; - @prop minMinaCandidate: UInt64; - @prop maxMinaCandidate: UInt64; - - constructor( - minMinaVote: UInt64, - minMinaCandidate: UInt64, - maxMinaCandidate: UInt64 - ) { - super(); - this.minMinaVote = minMinaVote; - this.minMinaCandidate = minMinaCandidate; - this.maxMinaCandidate = maxMinaCandidate; - } -} diff --git a/src/examples/zkapps/voting/preconditions.ts b/src/examples/zkapps/voting/preconditions.ts index 7d244d0113..c8dccfab59 100644 --- a/src/examples/zkapps/voting/preconditions.ts +++ b/src/examples/zkapps/voting/preconditions.ts @@ -1,4 +1,4 @@ -import { Bool, UInt32, UInt64 } from 'snarkyjs'; +import { Bool, UInt32, UInt64 } from 'o1js'; export class ElectionPreconditions { startElection: UInt32; @@ -17,7 +17,7 @@ export class ElectionPreconditions { export class ParticipantPreconditions { minMina: UInt64; - maxMina: UInt64; // have to make this "generic" so it applys for both candidate and voter instances + maxMina: UInt64; // have to make this "generic" so it applies for both candidate and voter instances static get default(): ParticipantPreconditions { return new ParticipantPreconditions(UInt64.zero, UInt64.MAXINT()); diff --git a/src/examples/zkapps/voting/run_berkeley.ts b/src/examples/zkapps/voting/run-berkeley.ts similarity index 90% rename from src/examples/zkapps/voting/run_berkeley.ts rename to src/examples/zkapps/voting/run-berkeley.ts index 04aeaa3cd4..38ed59b2b2 100644 --- a/src/examples/zkapps/voting/run_berkeley.ts +++ b/src/examples/zkapps/voting/run-berkeley.ts @@ -2,25 +2,22 @@ import { AccountUpdate, Bool, fetchAccount, - isReady, Mina, PrivateKey, PublicKey, Reducer, - shutdown, SmartContract, UInt32, UInt64, -} from 'snarkyjs'; +} from 'o1js'; import { VotingApp, VotingAppParams } from './factory.js'; import { Member, MyMerkleWitness } from './member.js'; -import { OffchainStorage } from './off_chain_storage.js'; +import { OffchainStorage } from './off-chain-storage.js'; import { ParticipantPreconditions, ElectionPreconditions, } from './preconditions.js'; -import { getResults, vote } from './voting_lib.js'; -await isReady; +import { getResults, vote } from './voting-lib.js'; const Berkeley = Mina.Network({ mina: 'https://proxy.berkeley.minaexplorer.com/graphql', @@ -91,14 +88,14 @@ let tx = await Mina.transaction( fee: 10_000_000, memo: 'Deploying contracts', }, - () => { + async () => { AccountUpdate.fundNewAccount(feePayerAddress, 3); - contracts.voting.deploy({ zkappKey: params.votingKey }); + await contracts.voting.deploy(); contracts.voting.committedVotes.set(storage.votesStore.getRoot()); contracts.voting.accumulatedVotes.set(Reducer.initialActionState); - contracts.candidateContract.deploy({ zkappKey: params.candidateKey }); + await contracts.candidateContract.deploy(); contracts.candidateContract.committedMembers.set( storage.candidatesStore.getRoot() ); @@ -106,13 +103,17 @@ let tx = await Mina.transaction( Reducer.initialActionState ); - contracts.voterContract.deploy({ zkappKey: params.voterKey }); + await contracts.voterContract.deploy(); contracts.voterContract.committedMembers.set(storage.votersStore.getRoot()); contracts.voterContract.accumulatedMembers.set(Reducer.initialActionState); } ); await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(); +await ( + await tx + .sign([feePayerKey, params.votingKey, params.candidateKey, params.voterKey]) + .send() +).wait(); console.log('successfully deployed contracts'); @@ -125,13 +126,13 @@ tx = await Mina.transaction( fee: 10_000_000, memo: 'Registering a voter', }, - () => { + async () => { let m = registerMember( 0n, Member.from(members[0], UInt64.from(150)), storage.votersStore ); - contracts.voting.voterRegistration(m); + await contracts.voting.voterRegistration(m); } ); await tx.prove(); @@ -147,13 +148,13 @@ tx = await Mina.transaction( fee: 10_000_000, memo: 'Registering a candidate', }, - () => { + async () => { let m = registerMember( 0n, Member.from(members[1], UInt64.from(150)), storage.candidatesStore ); - contracts.voting.candidateRegistration(m); + await contracts.voting.candidateRegistration(m); } ); await tx.prove(); @@ -171,8 +172,8 @@ tx = await Mina.transaction( fee: 10_000_000, memo: 'Approving registrations', }, - () => { - contracts.voting.approveRegistrations(); + async () => { + await contracts.voting.approveRegistrations(); } ); await tx.prove(); @@ -184,7 +185,7 @@ await fetchAllAccounts(); console.log('voting for a candidate'); tx = await Mina.transaction( { sender: feePayerAddress, fee: 10_000_000, memo: 'Casting vote' }, - () => { + async () => { let currentCandidate = storage.candidatesStore.get(0n)!; currentCandidate.witness = new MyMerkleWitness( @@ -198,7 +199,7 @@ tx = await Mina.transaction( v.witness = new MyMerkleWitness(storage.votersStore.getWitness(0n)); console.log(v.witness.calculateRoot(v.getHash()).toString()); console.log(contracts.voting.committedVotes.get().toString()); - contracts.voting.vote(currentCandidate, v); + await contracts.voting.vote(currentCandidate, v); } ); await tx.prove(); @@ -212,8 +213,8 @@ await fetchAllAccounts(); console.log('counting votes'); tx = await Mina.transaction( { sender: feePayerAddress, fee: 10_000_000, memo: 'Counting votes' }, - () => { - contracts.voting.countVotes(); + async () => { + await contracts.voting.countVotes(); } ); await tx.prove(); @@ -272,5 +273,3 @@ function registerMember( m.witness = new MyMerkleWitness(store.getWitness(i)); return m; } - -shutdown(); diff --git a/src/examples/zkapps/voting/run.ts b/src/examples/zkapps/voting/run.ts index e4ad05e26d..b632ee2f69 100644 --- a/src/examples/zkapps/voting/run.ts +++ b/src/examples/zkapps/voting/run.ts @@ -1,18 +1,18 @@ -import { Bool, PrivateKey, UInt32, UInt64 } from 'snarkyjs'; +import { Bool, PrivateKey, UInt32, UInt64 } from 'o1js'; import { VotingApp, VotingAppParams } from './factory.js'; import { ElectionPreconditions, ParticipantPreconditions, } from './preconditions.js'; -import { OffchainStorage } from './off_chain_storage.js'; +import { OffchainStorage } from './off-chain-storage.js'; import { Member } from './member.js'; import { testSet } from './test.js'; -import { getProfiler } from '../../profiler.js'; +import { getProfiler } from '../../utils/profiler.js'; console.log('Running Voting script...'); -// I really hope this factory pattern works with SnarkyJS' contracts +// I really hope this factory pattern works with o1js' contracts // one voting instance always consists of three contracts: two membership contracts and one voting contract // this pattern will hopefully help us deploy multiple sets of voting apps // with different preconditions efficiently for integration tests diff --git a/src/examples/zkapps/voting/test.ts b/src/examples/zkapps/voting/test.ts index f6f9959011..3e63c586ab 100644 --- a/src/examples/zkapps/voting/test.ts +++ b/src/examples/zkapps/voting/test.ts @@ -6,20 +6,20 @@ import { UInt64, UInt32, Permissions, -} from 'snarkyjs'; -import { deployContracts, deployInvalidContracts } from './deployContracts.js'; -import { DummyContract } from './dummyContract.js'; +} from 'o1js'; +import { deployContracts, deployInvalidContracts } from './deploy-contracts.js'; +import { DummyContract } from './dummy-contract.js'; import { VotingAppParams } from './factory.js'; import { Member, MyMerkleWitness } from './member.js'; import { Membership_ } from './membership.js'; -import { OffchainStorage } from './off_chain_storage.js'; +import { OffchainStorage } from './off-chain-storage.js'; import { Voting_ } from './voting.js'; import { assertValidTx, getResults, registerMember, vote, -} from './voting_lib.js'; +} from './voting-lib.js'; type Votes = OffchainStorage; type Candidates = OffchainStorage; @@ -84,8 +84,8 @@ export async function testSet( await assertValidTx( true, - () => { - verificationKeySet.voting.voterRegistration(m); + async () => { + await verificationKeySet.voting.voterRegistration(m); }, verificationKeySet.feePayer ); @@ -95,14 +95,16 @@ export async function testSet( await assertValidTx( true, - () => { - let vkUpdate = AccountUpdate.createSigned(params.votingKey); + async () => { + let vkUpdate = AccountUpdate.createSigned( + params.votingKey.toPublicKey() + ); vkUpdate.account.verificationKey.set({ ...verificationKey, hash: Field(verificationKey.hash), }); }, - verificationKeySet.feePayer + [verificationKeySet.feePayer, params.votingKey] ); m = Member.from(PrivateKey.random().toPublicKey(), UInt64.from(15)); @@ -110,8 +112,8 @@ export async function testSet( await assertValidTx( false, - () => { - verificationKeySet.voting.voterRegistration(m); + async () => { + await verificationKeySet.voting.voterRegistration(m); }, verificationKeySet.feePayer, 'Invalid proof' @@ -156,8 +158,8 @@ export async function testSet( await assertValidTx( true, - () => { - permissionedSet.voting.voterRegistration(m); + async () => { + await permissionedSet.voting.voterRegistration(m); }, permissionedSet.feePayer ); @@ -166,8 +168,10 @@ export async function testSet( await assertValidTx( true, - () => { - let permUpdate = AccountUpdate.createSigned(params.voterKey); + async () => { + let permUpdate = AccountUpdate.createSigned( + params.voterKey.toPublicKey() + ); permUpdate.account.permissions.set({ ...Permissions.default(), @@ -175,7 +179,7 @@ export async function testSet( editActionState: Permissions.impossible(), }); }, - permissionedSet.feePayer + [permissionedSet.feePayer, params.voterKey] ); console.log('trying to invoke method with invalid permissions...'); @@ -185,8 +189,8 @@ export async function testSet( await assertValidTx( false, - () => { - permissionedSet.voting.voterRegistration(m); + async () => { + await permissionedSet.voting.voterRegistration(m); }, permissionedSet.feePayer, 'actions' @@ -228,9 +232,12 @@ export async function testSet( invalidSet.Local.addAccount(m.publicKey, m.balance.toString()); try { - let tx = await Mina.transaction(invalidSet.feePayer.toPublicKey(), () => { - invalidSet.voting.voterRegistration(m); - }); + let tx = await Mina.transaction( + invalidSet.feePayer.toPublicKey(), + async () => { + await invalidSet.voting.voterRegistration(m); + } + ); await tx.prove(); await tx.sign([invalidSet.feePayer]).send(); } catch (err: any) { @@ -279,7 +286,7 @@ export async function testSet( try { let tx = await Mina.transaction( sequenceOverflowSet.feePayer.toPublicKey(), - () => { + async () => { let m = Member.from( PrivateKey.random().toPublicKey(), @@ -290,7 +297,7 @@ export async function testSet( m.balance.toString() ); - sequenceOverflowSet.voting.voterRegistration(m); + await sequenceOverflowSet.voting.voterRegistration(m); } ); await tx.prove(); @@ -311,8 +318,8 @@ export async function testSet( try { let tx = await Mina.transaction( sequenceOverflowSet.feePayer.toPublicKey(), - () => { - sequenceOverflowSet.voting.approveRegistrations(); + async () => { + await sequenceOverflowSet.voting.approveRegistrations(); } ); await tx.prove(); @@ -381,8 +388,8 @@ export async function testSet( await assertValidTx( true, - () => { - voting.voterRegistration(newVoter1); + async () => { + await voting.voterRegistration(newVoter1); }, feePayer ); @@ -437,8 +444,8 @@ export async function testSet( await assertValidTx( false, - () => { - voting.voterRegistration(newVoterLow); + async () => { + await voting.voterRegistration(newVoterLow); }, feePayer, 'Balance not high enough!' @@ -453,8 +460,8 @@ export async function testSet( await assertValidTx( false, - () => { - voting.voterRegistration(newVoterHigh); + async () => { + await voting.voterRegistration(newVoterHigh); }, feePayer, 'Balance too high!' @@ -464,8 +471,8 @@ export async function testSet( await assertValidTx( false, - () => { - voting.voterRegistration(newVoter1); + async () => { + await voting.voterRegistration(newVoter1); }, feePayer, 'Member already exists!' @@ -500,7 +507,7 @@ export async function testSet( await assertValidTx( true, - () => { + async () => { let newCandidate = registerMember( 0n, Member.from( @@ -513,7 +520,7 @@ export async function testSet( ); // register new candidate - voting.candidateRegistration(newCandidate); + await voting.candidateRegistration(newCandidate); }, feePayer ); @@ -522,7 +529,7 @@ export async function testSet( await assertValidTx( true, - () => { + async () => { let newCandidate = registerMember( 1n, Member.from( @@ -535,7 +542,7 @@ export async function testSet( ); // register new candidate - voting.candidateRegistration(newCandidate); + await voting.candidateRegistration(newCandidate); }, feePayer ); @@ -579,9 +586,9 @@ export async function testSet( await assertValidTx( true, - () => { + async () => { // register new candidate - voting.approveRegistrations(); + await voting.approveRegistrations(); }, feePayer ); @@ -647,9 +654,9 @@ export async function testSet( await assertValidTx( false, - () => { + async () => { // register late candidate - voting.candidateRegistration(lateCandidate); + await voting.candidateRegistration(lateCandidate); }, feePayer, 'Outside of election period!' @@ -667,9 +674,9 @@ export async function testSet( await assertValidTx( false, - () => { + async () => { // register late voter - voting.voterRegistration(lateVoter); + await voting.voterRegistration(lateVoter); }, feePayer, 'Outside of election period!' @@ -725,8 +732,8 @@ export async function testSet( let beforeCommitted = voting.committedVotes.get(); await assertValidTx( true, - () => { - voting.countVotes(); + async () => { + await voting.countVotes(); }, feePayer ); @@ -761,7 +768,7 @@ export async function testSet( await assertValidTx( true, - () => { + async () => { // attempting to vote for the registered candidate currentCandidate = candidatesStore.get(0n)!; currentCandidate.witness = new MyMerkleWitness( @@ -774,7 +781,7 @@ export async function testSet( let v = votersStore.get(0n)!; v.witness = new MyMerkleWitness(votersStore.getWitness(0n)); - voting.vote(currentCandidate, v); + await voting.vote(currentCandidate, v); }, feePayer ); @@ -816,10 +823,10 @@ export async function testSet( await assertValidTx( false, - () => { + async () => { // attempting to vote for the registered candidate - voting.vote(fakeCandidate, votersStore.get(0n)!); + await voting.vote(fakeCandidate, votersStore.get(0n)!); }, feePayer, 'Member is not a candidate!' @@ -835,8 +842,8 @@ export async function testSet( await assertValidTx( false, - () => { - voting.vote(fakeVoter, votersStore.get(0n)!); + async () => { + await voting.vote(fakeVoter, votersStore.get(0n)!); }, feePayer, 'Member is not a candidate!' @@ -846,9 +853,9 @@ export async function testSet( await assertValidTx( false, - () => { + async () => { const voter = votersStore.get(0n)!; - voting.vote(voter, votersStore.get(0n)!); + await voting.vote(voter, votersStore.get(0n)!); }, feePayer, 'Member is not a candidate!' @@ -874,8 +881,8 @@ export async function testSet( await assertValidTx( true, - () => { - voting.countVotes(); + async () => { + await voting.countVotes(); }, feePayer ); @@ -927,8 +934,8 @@ export async function testSet( await assertValidTx( false, - () => { - voting.voterRegistration(voter); + async () => { + await voting.voterRegistration(voter); }, feePayer, 'Outside of election period!' @@ -944,8 +951,8 @@ export async function testSet( await assertValidTx( false, - () => { - voting.candidateRegistration(candidate); + async () => { + await voting.candidateRegistration(candidate); }, feePayer, 'Outside of election period!' diff --git a/src/examples/zkapps/voting/voting_lib.ts b/src/examples/zkapps/voting/voting-lib.ts similarity index 91% rename from src/examples/zkapps/voting/voting_lib.ts rename to src/examples/zkapps/voting/voting-lib.ts index 707290d840..ece6d5f03e 100644 --- a/src/examples/zkapps/voting/voting_lib.ts +++ b/src/examples/zkapps/voting/voting-lib.ts @@ -1,8 +1,8 @@ import { Member, MyMerkleWitness } from './member.js'; -import { OffchainStorage } from './off_chain_storage.js'; +import { OffchainStorage } from './off-chain-storage.js'; import { Voting_ } from './voting.js'; -import { Mina, PrivateKey } from 'snarkyjs'; -import { Printer } from 'prettier'; +import { Mina, PrivateKey } from 'o1js'; + /** * Updates off-chain storage when registering a member or candidate * @param {bigint} i index of memberStore or candidatesStore @@ -72,16 +72,18 @@ export function getResults( */ export async function assertValidTx( expectToBeValid: boolean, - cb: () => void, - feePayer: PrivateKey, + cb: () => Promise, + signers: PrivateKey | [PrivateKey, ...PrivateKey[]], msg?: string ) { let failed = false; let err; + if (!Array.isArray(signers)) signers = [signers]; + let [feePayer] = signers; try { let tx = await Mina.transaction(feePayer.toPublicKey(), cb); await tx.prove(); - await tx.sign([feePayer]).send(); + await tx.sign(signers).send(); } catch (e: any) { failed = true; err = e; diff --git a/src/examples/zkapps/voting/voting.ts b/src/examples/zkapps/voting/voting.ts index 034eaf3f93..84f526aaec 100644 --- a/src/examples/zkapps/voting/voting.ts +++ b/src/examples/zkapps/voting/voting.ts @@ -4,7 +4,6 @@ import { state, State, method, - DeployArgs, Permissions, PublicKey, Bool, @@ -13,7 +12,7 @@ import { AccountUpdate, Provable, TransactionVersion, -} from 'snarkyjs'; +} from 'o1js'; import { Member } from './member.js'; import { @@ -47,7 +46,7 @@ let voterPreconditions = ParticipantPreconditions.default; */ let electionPreconditions = ElectionPreconditions.default; -interface VotingParams { +type VotingParams = { electionPreconditions: ElectionPreconditions; voterPreconditions: ParticipantPreconditions; candidatePreconditions: ParticipantPreconditions; @@ -55,7 +54,7 @@ interface VotingParams { voterAddress: PublicKey; contractAddress: PublicKey; doProofs: boolean; -} +}; /** * Returns a new contract instance that based on a set of preconditions. @@ -92,13 +91,13 @@ export class Voting_ extends SmartContract { events = { newVoteFor: PublicKey, newVoteState: provablePure({ - committedVotesRoot: Field, accumulatedVotesRoot: Field, + committedVotesRoot: Field, }), }; - deploy(args: DeployArgs) { - super.deploy(args); + async deploy() { + await super.deploy(); this.account.permissions.set({ ...Permissions.default(), editState: Permissions.proofOrSignature(), @@ -118,9 +117,9 @@ export class Voting_ extends SmartContract { * @param member */ @method - voterRegistration(member: Member) { + async voterRegistration(member: Member) { let currentSlot = this.network.globalSlotSinceGenesis.get(); - this.network.globalSlotSinceGenesis.assertBetween( + this.network.globalSlotSinceGenesis.requireBetween( currentSlot, currentSlot.add(10) ); @@ -137,7 +136,7 @@ export class Voting_ extends SmartContract { let accountUpdate = AccountUpdate.create(member.publicKey); - accountUpdate.account.balance.assertEquals( + accountUpdate.account.balance.requireEquals( accountUpdate.account.balance.get() ); @@ -153,7 +152,7 @@ export class Voting_ extends SmartContract { ); let VoterContract: Membership_ = new Membership_(voterAddress); - let exists = VoterContract.addEntry(member); + let exists = await VoterContract.addEntry(member); // the check happens here because we want to see if the other contract returns a value // if exists is true, that means the member already exists within the accumulated state @@ -167,9 +166,9 @@ export class Voting_ extends SmartContract { * @param member */ @method - candidateRegistration(member: Member) { + async candidateRegistration(member: Member) { let currentSlot = this.network.globalSlotSinceGenesis.get(); - this.network.globalSlotSinceGenesis.assertBetween( + this.network.globalSlotSinceGenesis.requireBetween( currentSlot, currentSlot.add(10) ); @@ -186,7 +185,7 @@ export class Voting_ extends SmartContract { // this snippet pulls the account data of an address from the network let accountUpdate = AccountUpdate.create(member.publicKey); - accountUpdate.account.balance.assertEquals( + accountUpdate.account.balance.requireEquals( accountUpdate.account.balance.get() ); @@ -202,7 +201,7 @@ export class Voting_ extends SmartContract { ); let CandidateContract: Membership_ = new Membership_(candidateAddress); - let exists = CandidateContract.addEntry(member); + let exists = await CandidateContract.addEntry(member); // the check happens here because we want to see if the other contract returns a value // if exists is true, that means the member already exists within the accumulated state @@ -215,13 +214,13 @@ export class Voting_ extends SmartContract { * Calls the `publish()` method of the Candidate-Membership and Voter-Membership contract. */ @method - approveRegistrations() { + async approveRegistrations() { // Invokes the publish method of both Voter and Candidate Membership contracts. let VoterContract: Membership_ = new Membership_(voterAddress); - VoterContract.publish(); + await VoterContract.publish(); let CandidateContract: Membership_ = new Membership_(candidateAddress); - CandidateContract.publish(); + await CandidateContract.publish(); } /** @@ -231,9 +230,9 @@ export class Voting_ extends SmartContract { * @param voter */ @method - vote(candidate: Member, voter: Member) { + async vote(candidate: Member, voter: Member) { let currentSlot = this.network.globalSlotSinceGenesis.get(); - this.network.globalSlotSinceGenesis.assertBetween( + this.network.globalSlotSinceGenesis.requireBetween( currentSlot, currentSlot.add(10) ); @@ -250,10 +249,10 @@ export class Voting_ extends SmartContract { // verifying that both the voter and the candidate are actually part of our member set // ideally we would also verify a signature here, but ignoring that for now let VoterContract: Membership_ = new Membership_(voterAddress); - VoterContract.isMember(voter).assertTrue('Member is not a voter!'); + (await VoterContract.isMember(voter)).assertTrue('Member is not a voter!'); let CandidateContract: Membership_ = new Membership_(candidateAddress); - CandidateContract.isMember(candidate).assertTrue( + (await CandidateContract.isMember(candidate)).assertTrue( 'Member is not a candidate!' ); @@ -268,12 +267,12 @@ export class Voting_ extends SmartContract { * and applies state changes to the votes merkle tree. */ @method - countVotes() { + async countVotes() { let accumulatedVotes = this.accumulatedVotes.get(); - this.accumulatedVotes.assertEquals(accumulatedVotes); + this.accumulatedVotes.requireEquals(accumulatedVotes); let committedVotes = this.committedVotes.get(); - this.committedVotes.assertEquals(committedVotes); + this.committedVotes.requireEquals(committedVotes); let { state: newCommittedVotes, actionState: newAccumulatedVotes } = this.reducer.reduce( @@ -283,7 +282,7 @@ export class Voting_ extends SmartContract { // apply one vote action = action.addVote(); // this is the new root after we added one vote - return action.votesWitness.calculateRootSlow(action.getHash()); + return action.votesWitness.calculateRoot(action.getHash()); }, // initial state { state: committedVotes, actionState: accumulatedVotes } diff --git a/src/examples/zkapps/zkapp-self-update.ts b/src/examples/zkapps/zkapp-self-update.ts index 9baa9770ce..e7d03c347a 100644 --- a/src/examples/zkapps/zkapp-self-update.ts +++ b/src/examples/zkapps/zkapp-self-update.ts @@ -6,15 +6,12 @@ import { VerificationKey, method, Permissions, - isReady, PrivateKey, Mina, AccountUpdate, - Circuit, Provable, TransactionVersion, - UInt32, -} from 'snarkyjs'; +} from 'o1js'; class Foo extends SmartContract { init() { @@ -28,21 +25,19 @@ class Foo extends SmartContract { }); } - @method replaceVerificationKey(verificationKey: VerificationKey) { + @method async replaceVerificationKey(verificationKey: VerificationKey) { this.account.verificationKey.set(verificationKey); } } class Bar extends SmartContract { - @method call() { + @method async call() { Provable.log('Bar'); } } // setup -await isReady; - const Local = Mina.LocalBlockchain({ proofsEnabled: true }); Mina.setActiveInstance(Local); @@ -57,9 +52,9 @@ const { privateKey: deployerKey, publicKey: deployerAccount } = await Foo.compile(); -const tx = await Mina.transaction(deployerAccount, () => { +const tx = await Mina.transaction(deployerAccount, async () => { AccountUpdate.fundNewAccount(deployerAccount); - zkApp.deploy(); + await zkApp.deploy(); }); await tx.prove(); await tx.sign([deployerKey, zkAppPrivateKey]).send(); @@ -71,8 +66,8 @@ Provable.log('original verification key', fooVerificationKey); const { verificationKey: barVerificationKey } = await Bar.compile(); -const tx2 = await Mina.transaction(deployerAccount, () => { - zkApp.replaceVerificationKey(barVerificationKey); +const tx2 = await Mina.transaction(deployerAccount, async () => { + await zkApp.replaceVerificationKey(barVerificationKey); }); await tx2.prove(); await tx2.sign([deployerKey]).send(); diff --git a/src/examples/zkprogram/README.md b/src/examples/zkprogram/README.md new file mode 100644 index 0000000000..5386fe855f --- /dev/null +++ b/src/examples/zkprogram/README.md @@ -0,0 +1,3 @@ +# ZkProgram + +These examples focus on how to use `ZkProgram`, our main API for creating proofs outside of smart contract. diff --git a/src/examples/zkprogram/gadgets.ts b/src/examples/zkprogram/gadgets.ts new file mode 100644 index 0000000000..e3cdb0aabe --- /dev/null +++ b/src/examples/zkprogram/gadgets.ts @@ -0,0 +1,78 @@ +import { Field, Provable, Gadgets, ZkProgram } from 'o1js'; + +let cs = await Provable.constraintSystem(() => { + let f = Provable.witness(Field, () => Field(12)); + + let res1 = Gadgets.rotate64(f, 2, 'left'); + let res2 = Gadgets.rotate64(f, 2, 'right'); + + res1.assertEquals(Field(48)); + res2.assertEquals(Field(3)); + + Provable.log(res1); + Provable.log(res2); +}); +console.log('constraint system: ', cs); + +const BitwiseProver = ZkProgram({ + name: 'bitwise', + methods: { + rot: { + privateInputs: [], + async method() { + let a = Provable.witness(Field, () => Field(48)); + let actualLeft = Gadgets.rotate64(a, 2, 'left'); + let actualRight = Gadgets.rotate64(a, 2, 'right'); + + let expectedLeft = Field(192); + actualLeft.assertEquals(expectedLeft); + + let expectedRight = Field(12); + actualRight.assertEquals(expectedRight); + }, + }, + xor: { + privateInputs: [], + async method() { + let a = Provable.witness(Field, () => Field(5)); + let b = Provable.witness(Field, () => Field(2)); + let actual = Gadgets.xor(a, b, 4); + let expected = Field(7); + actual.assertEquals(expected); + }, + }, + and: { + privateInputs: [], + async method() { + let a = Provable.witness(Field, () => Field(3)); + let b = Provable.witness(Field, () => Field(5)); + let actual = Gadgets.and(a, b, 4); + let expected = Field(1); + actual.assertEquals(expected); + }, + }, + }, +}); + +console.log('compiling..'); + +console.time('compile'); +await BitwiseProver.compile(); +console.timeEnd('compile'); + +console.log('proving..'); + +console.time('rotation prove'); +let rotProof = await BitwiseProver.rot(); +console.timeEnd('rotation prove'); +if (!(await BitwiseProver.verify(rotProof))) throw Error('rot: Invalid proof'); + +console.time('xor prove'); +let xorProof = await BitwiseProver.xor(); +console.timeEnd('xor prove'); +if (!(await BitwiseProver.verify(xorProof))) throw Error('xor: Invalid proof'); + +console.time('and prove'); +let andProof = await BitwiseProver.and(); +console.timeEnd('and prove'); +if (!(await BitwiseProver.verify(andProof))) throw Error('and: Invalid proof'); diff --git a/src/examples/program-with-input.ts b/src/examples/zkprogram/program-with-input.ts similarity index 86% rename from src/examples/program-with-input.ts rename to src/examples/zkprogram/program-with-input.ts index 2bdb552fac..b50cf4c65f 100644 --- a/src/examples/program-with-input.ts +++ b/src/examples/zkprogram/program-with-input.ts @@ -1,30 +1,28 @@ import { SelfProof, Field, - Experimental, + ZkProgram, verify, - isReady, Proof, JsonProof, Provable, -} from 'snarkyjs'; +} from 'o1js'; -await isReady; - -let MyProgram = Experimental.ZkProgram({ +let MyProgram = ZkProgram({ + name: 'example-with-input', publicInput: Field, methods: { baseCase: { privateInputs: [], - method(input: Field) { + async method(input: Field) { input.assertEquals(Field(0)); }, }, inductiveCase: { privateInputs: [SelfProof], - method(input: Field, earlierProof: SelfProof) { + async method(input: Field, earlierProof: SelfProof) { earlierProof.verify(); earlierProof.publicInput.add(1).assertEquals(input); }, @@ -35,13 +33,13 @@ let MyProgram = Experimental.ZkProgram({ MyProgram.publicInputType satisfies typeof Field; MyProgram.publicOutputType satisfies Provable; -let MyProof = Experimental.ZkProgram.Proof(MyProgram); +let MyProof = ZkProgram.Proof(MyProgram); console.log('program digest', MyProgram.digest()); console.log('compiling MyProgram...'); let { verificationKey } = await MyProgram.compile(); -console.log('verification key', verificationKey.slice(0, 10) + '..'); +console.log('verification key', verificationKey.data.slice(0, 10) + '..'); console.log('proving base case...'); let proof = await MyProgram.baseCase(Field(0)); diff --git a/src/examples/program.ts b/src/examples/zkprogram/program.ts similarity index 86% rename from src/examples/program.ts rename to src/examples/zkprogram/program.ts index 7c364e572d..b5d6036df8 100644 --- a/src/examples/program.ts +++ b/src/examples/zkprogram/program.ts @@ -1,31 +1,29 @@ import { SelfProof, Field, - Experimental, + ZkProgram, verify, - isReady, Proof, JsonProof, Provable, Empty, -} from 'snarkyjs'; +} from 'o1js'; -await isReady; - -let MyProgram = Experimental.ZkProgram({ +let MyProgram = ZkProgram({ + name: 'example-with-output', publicOutput: Field, methods: { baseCase: { privateInputs: [], - method() { + async method() { return Field(0); }, }, inductiveCase: { privateInputs: [SelfProof], - method(earlierProof: SelfProof) { + async method(earlierProof: SelfProof) { earlierProof.verify(); return earlierProof.publicOutput.add(1); }, @@ -36,13 +34,13 @@ let MyProgram = Experimental.ZkProgram({ MyProgram.publicInputType satisfies Provable; MyProgram.publicOutputType satisfies typeof Field; -let MyProof = Experimental.ZkProgram.Proof(MyProgram); +let MyProof = ZkProgram.Proof(MyProgram); console.log('program digest', MyProgram.digest()); console.log('compiling MyProgram...'); let { verificationKey } = await MyProgram.compile(); -console.log('verification key', verificationKey.slice(0, 10) + '..'); +console.log('verification key', verificationKey.data.slice(0, 10) + '..'); console.log('proving base case...'); let proof = await MyProgram.baseCase(); diff --git a/src/index.ts b/src/index.ts index 5e684ddb03..681f78e452 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,41 +1,72 @@ -export type { ProvablePure } from './snarky.js'; +export type { ProvablePure } from './lib/provable/types/provable-intf.js'; export { Ledger } from './snarky.js'; -export { Field, Bool, Group, Scalar } from './lib/core.js'; -export { Poseidon, TokenSymbol } from './lib/hash.js'; -export * from './lib/signature.js'; +export { Field, Bool, Group, Scalar } from './lib/provable/wrapped.js'; +export { + createForeignField, + ForeignField, + AlmostForeignField, + CanonicalForeignField, +} from './lib/provable/foreign-field.js'; +export { + createForeignCurve, + ForeignCurve, +} from './lib/provable/crypto/foreign-curve.js'; +export { + createEcdsa, + EcdsaSignature, +} from './lib/provable/crypto/foreign-ecdsa.js'; +export { + Poseidon, + TokenSymbol, + ProvableHashable, +} from './lib/provable/crypto/poseidon.js'; +export { Keccak } from './lib/provable/crypto/keccak.js'; +export { Hash } from './lib/provable/crypto/hash.js'; + +export { assert } from './lib/provable/gadgets/common.js'; + +export * from './lib/provable/crypto/signature.js'; export type { ProvableExtended, FlexibleProvable, FlexibleProvablePure, InferProvable, -} from './lib/circuit_value.js'; +} from './lib/provable/types/struct.js'; +export { provable, provablePure, Struct } from './lib/provable/types/struct.js'; +export { Unconstrained } from './lib/provable/types/unconstrained.js'; +export { Provable } from './lib/provable/provable.js'; export { - CircuitValue, - prop, - arrayProp, - matrixProp, - provable, - provablePure, - Struct, -} from './lib/circuit_value.js'; -export { Provable } from './lib/provable.js'; -export { Circuit, Keypair, public_, circuitMain } from './lib/circuit.js'; -export { UInt32, UInt64, Int64, Sign } from './lib/int.js'; + Circuit, + Keypair, + public_, + circuitMain, +} from './lib/proof-system/circuit.js'; +export { UInt32, UInt64, Int64, Sign, UInt8 } from './lib/provable/int.js'; +export { Bytes } from './lib/provable/wrapped-classes.js'; +export { Packed, Hashed } from './lib/provable/packed.js'; +export { Gadgets } from './lib/provable/gadgets/gadgets.js'; export { Types } from './bindings/mina-transaction/types.js'; -export * as Mina from './lib/mina.js'; -export type { DeployArgs } from './lib/zkapp.js'; +export { MerkleList, MerkleListIterator } from './lib/provable/merkle-list.js'; + +export * as Mina from './lib/mina/mina.js'; +export { + type Transaction, + type PendingTransaction, + type IncludedTransaction, + type RejectedTransaction, +} from './lib/mina/transaction.js'; +export type { DeployArgs } from './lib/mina/zkapp.js'; export { SmartContract, method, declareMethods, Account, - VerificationKey, Reducer, -} from './lib/zkapp.js'; -export { state, State, declareState } from './lib/state.js'; +} from './lib/mina/zkapp.js'; +export { state, State, declareState } from './lib/mina/state.js'; -export type { JsonProof } from './lib/proof_system.js'; +export type { JsonProof } from './lib/proof-system/zkprogram.js'; export { Proof, SelfProof, @@ -43,18 +74,24 @@ export { Empty, Undefined, Void, -} from './lib/proof_system.js'; + VerificationKey, +} from './lib/proof-system/zkprogram.js'; +export { Cache, CacheHeader } from './lib/proof-system/cache.js'; export { - Token, TokenId, AccountUpdate, Permissions, ZkappPublicInput, TransactionVersion, -} from './lib/account_update.js'; + AccountUpdateForest, + AccountUpdateTree, +} from './lib/mina/account-update.js'; + +export { TokenAccountUpdateIterator } from './lib/mina/token/forest-iterator.js'; +export { TokenContract } from './lib/mina/token/token-contract.js'; -export type { TransactionStatus } from './lib/fetch.js'; +export type { TransactionStatus } from './lib/mina/graphql.js'; export { fetchAccount, fetchLastBlock, @@ -66,54 +103,38 @@ export { setGraphqlEndpoints, setArchiveGraphqlEndpoint, sendZkapp, -} from './lib/fetch.js'; -export * as Encryption from './lib/encryption.js'; + Lightnet, +} from './lib/mina/fetch.js'; +export * as Encryption from './lib/provable/crypto/encryption.js'; export * as Encoding from './bindings/lib/encoding.js'; -export { Character, CircuitString } from './lib/string.js'; -export { MerkleTree, MerkleWitness } from './lib/merkle_tree.js'; -export { MerkleMap, MerkleMapWitness } from './lib/merkle_map.js'; +export { Character, CircuitString } from './lib/provable/string.js'; +export { MerkleTree, MerkleWitness } from './lib/provable/merkle-tree.js'; +export { MerkleMap, MerkleMapWitness } from './lib/provable/merkle-map.js'; + +export { Nullifier } from './lib/provable/crypto/nullifier.js'; -export { Nullifier } from './lib/nullifier.js'; +export { ZkProgram } from './lib/proof-system/zkprogram.js'; + +export { Crypto } from './lib/provable/crypto/crypto.js'; + +export type { NetworkId } from './mina-signer/mina-signer.js'; + +export { setNumberOfWorkers } from './lib/proof-system/workers.js'; // experimental APIs -import { ZkProgram } from './lib/proof_system.js'; -import { Callback } from './lib/zkapp.js'; -import { createChildAccountUpdate } from './lib/account_update.js'; -import { memoizeWitness } from './lib/provable.js'; +import { memoizeWitness } from './lib/provable/provable.js'; export { Experimental }; const Experimental_ = { - Callback, - createChildAccountUpdate, memoizeWitness, - ZkProgram, }; -type Callback_ = Callback; - /** * This module exposes APIs that are unstable, in the sense that the API surface is expected to change. * (Not unstable in the sense that they are less functional or tested than other parts.) */ namespace Experimental { - export let ZkProgram = Experimental_.ZkProgram; - export let createChildAccountUpdate = Experimental_.createChildAccountUpdate; export let memoizeWitness = Experimental_.memoizeWitness; - export let Callback = Experimental_.Callback; - export type Callback = Callback_; } -Error.stackTraceLimit = 1000; - -// deprecated stuff -export { isReady, shutdown }; - -/** - * @deprecated `await isReady` is no longer needed. Remove it from your code. - */ -let isReady = Promise.resolve(); - -/** - * @deprecated `shutdown()` is no longer needed, and is a no-op. Remove it from your code. - */ -function shutdown() {} +Error.stackTraceLimit = 100000; diff --git a/src/lib/caller.unit-test.ts b/src/lib/caller.unit-test.ts deleted file mode 100644 index 1c6508dcd6..0000000000 --- a/src/lib/caller.unit-test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { AccountUpdate, TokenId } from './account_update.js'; -import * as Mina from './mina.js'; -import { expect } from 'expect'; - -let Local = Mina.LocalBlockchain(); -Mina.setActiveInstance(Local); - -let [{ privateKey, publicKey }] = Local.testAccounts; - -let parentId = TokenId.derive(publicKey); - -/** - * tests whether the following two account updates gives the child token permissions: - * - * InheritFromParent -> ParentsOwnToken - */ -let tx = await Mina.transaction(privateKey, () => { - let parent = AccountUpdate.defaultAccountUpdate(publicKey); - parent.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent; - parent.balance.subInPlace(Mina.accountCreationFee()); - - let child = AccountUpdate.defaultAccountUpdate(publicKey, parentId); - child.body.mayUseToken = AccountUpdate.MayUseToken.ParentsOwnToken; - - AccountUpdate.attachToTransaction(parent); - parent.approve(child); -}); - -// according to this test, the child doesn't get token permissions -await expect(tx.send()).rejects.toThrow( - 'can not use or pass on token permissions' -); diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts deleted file mode 100644 index cf9966311e..0000000000 --- a/src/lib/field.unit-test.ts +++ /dev/null @@ -1,342 +0,0 @@ -import { ProvablePure } from '../snarky.js'; -import { Field } from './core.js'; -import { Field as Fp } from '../provable/field-bigint.js'; -import { test, Random } from './testing/property.js'; -import { deepEqual, throws } from 'node:assert/strict'; -import { Provable } from './provable.js'; -import { Binable } from '../bindings/lib/binable.js'; -import { ProvableExtended } from './circuit_value.js'; -import { FieldType } from './field.js'; - -// types -Field satisfies Provable; -Field satisfies ProvablePure; -Field satisfies ProvableExtended; -Field satisfies Binable; - -// constructor -test(Random.field, Random.json.field, (x, y, assert) => { - let z = Field(x); - assert(z instanceof Field); - assert(z.toBigInt() === x); - assert(z.toString() === x.toString()); - assert(z.isConstant()); - deepEqual(z.toConstant(), z); - - assert((z = new Field(x)) instanceof Field && z.toBigInt() === x); - assert((z = Field(z)) instanceof Field && z.toBigInt() === x); - assert((z = Field(z.value)) instanceof Field && z.toBigInt() === x); - - z = Field(y); - assert(z instanceof Field); - assert(z.toString() === y); - deepEqual(Field.fromJSON(y), z); - assert(z.toJSON() === y); -}); - -// handles small numbers -test(Random.nat(1000), (n, assert) => { - assert(Field(n).toString() === String(n)); -}); -// handles large numbers 2^31 <= x < 2^53 -test(Random.int(2 ** 31, Number.MAX_SAFE_INTEGER), (n, assert) => { - assert(Field(n).toString() === String(n)); -}); -// handles negative numbers -test(Random.uint32, (n) => { - deepEqual(Field(-n), Field(n).neg()); -}); -// throws on fractional numbers -test.negative(Random.int(-10, 10), Random.fraction(1), (x, f) => { - Field(x + f); -}); -// correctly overflows the field -test(Random.field, Random.int(-5, 5), (x, k) => { - deepEqual(Field(x + BigInt(k) * Field.ORDER), Field(x)); -}); - -// special generator -let SmallField = Random.reject( - Random.field, - (x) => x.toString(2).length > Fp.sizeInBits - 2 -); - -// arithmetic, both in- and outside provable code -equivalent2((x, y) => x.add(y), Fp.add); -equivalent1((x) => x.neg(), Fp.negate); -equivalent2((x, y) => x.sub(y), Fp.sub); -equivalent2((x, y) => x.mul(y), Fp.mul); -equivalent1( - (x) => x.inv(), - (x) => Fp.inverse(x) ?? throwError('division by 0') -); -equivalent2( - (x, y) => x.div(y), - (x, y) => Fp.div(x, y) ?? throwError('division by 0') -); -equivalent1((x) => x.square(), Fp.square); -equivalent1( - (x) => x.sqrt(), - (x) => Fp.sqrt(x) ?? throwError('no sqrt') -); -equivalent2( - (x, y) => x.equals(y).toField(), - (x, y) => BigInt(x === y) -); -equivalent2( - (x, y) => x.lessThan(y).toField(), - (x, y) => BigInt(x < y), - SmallField -); -equivalent2( - (x, y) => x.lessThanOrEqual(y).toField(), - (x, y) => BigInt(x <= y), - SmallField -); -equivalentVoid2( - (x, y) => x.assertEquals(y), - (x, y) => x === y || throwError('not equal') -); -equivalentVoid2( - (x, y) => x.assertNotEquals(y), - (x, y) => x !== y || throwError('equal') -); -equivalentVoid2( - (x, y) => x.assertLessThan(y), - (x, y) => x < y || throwError('not less than'), - SmallField -); -equivalentVoid2( - (x, y) => x.assertLessThanOrEqual(y), - (x, y) => x <= y || throwError('not less than or equal'), - SmallField -); -equivalentVoid1( - (x) => x.assertBool(), - (x) => x === 0n || x === 1n || throwError('not boolean') -); -equivalent1( - (x) => x.isEven().toField(), - (x) => BigInt((x & 1n) === 0n), - SmallField -); - -// non-constant field vars -test(Random.field, (x0, assert) => { - Provable.runAndCheck(() => { - // Var - let x = Provable.witness(Field, () => Field(x0)); - assert(x.value[0] === FieldType.Var); - assert(typeof x.value[1] === 'number'); - throws(() => x.toConstant()); - throws(() => x.toBigInt()); - Provable.asProver(() => assert(x.toBigInt() === x0)); - - // Scale - let z = x.mul(2); - assert(z.value[0] === FieldType.Scale); - throws(() => x.toConstant()); - - // Add - let u = z.add(x); - assert(u.value[0] === FieldType.Add); - throws(() => x.toConstant()); - Provable.asProver(() => assert(u.toBigInt() === Fp.mul(x0, 3n))); - - // seal - let v = u.seal(); - assert(v.value[0] === FieldType.Var); - Provable.asProver(() => assert(v.toBigInt() === Fp.mul(x0, 3n))); - - // Provable.witness / assertEquals / assertNotEquals - let w0 = Provable.witness(Field, () => v.mul(5).add(1)); - let w1 = x.mul(15).add(1); - w0.assertEquals(w1); - throws(() => w0.assertNotEquals(w1)); - - let w2 = Provable.witness(Field, () => w0.add(1)); - w0.assertNotEquals(w2); - throws(() => w0.assertEquals(w2)); - }); -}); - -// some provable operations -test(Random.field, Random.field, (x0, y0, assert) => { - Provable.runAndCheck(() => { - // equals - let x = Provable.witness(Field, () => Field(x0)); - let y = Provable.witness(Field, () => Field(y0)); - - let b = x.equals(y); - b.assertEquals(x0 === y0); - Provable.asProver(() => assert(b.toBoolean() === (x0 === y0))); - - let c = x.equals(x0); - c.assertEquals(true); - Provable.asProver(() => assert(c.toBoolean())); - - // mul - let z = x.mul(y); - Provable.asProver(() => assert(z.toBigInt() === Fp.mul(x0, y0))); - - // toBits / fromBits - let bits = Fp.toBits(x0); - let x1 = Provable.witness(Field, () => Field.fromBits(bits)); - let bitsVars = x1.toBits(); - Provable.asProver(() => - assert(bitsVars.every((b, i) => b.toBoolean() === bits[i])) - ); - }); -}); - -// helpers - -function equivalent1( - op1: (x: Field) => Field, - op2: (x: bigint) => bigint, - rng: Random = Random.field -) { - test(rng, (x0, assert) => { - let x = Field(x0); - // outside provable code - handleErrors( - () => op1(x), - () => op2(x0), - (a, b) => assert(a.toBigInt() === b, 'equal results') - ); - // inside provable code - Provable.runAndCheck(() => { - x = Provable.witness(Field, () => x); - handleErrors( - () => op1(x), - () => op2(x0), - (a, b) => - Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) - ); - }); - }); -} -function equivalent2( - op1: (x: Field, y: Field | bigint) => Field, - op2: (x: bigint, y: bigint) => bigint, - rng: Random = Random.field -) { - test(rng, rng, (x0, y0, assert) => { - let x = Field(x0); - let y = Field(y0); - // outside provable code - handleErrors( - () => op1(x, y), - () => op2(x0, y0), - (a, b) => assert(a.toBigInt() === b, 'equal results') - ); - handleErrors( - () => op1(x, y0), - () => op2(x0, y0), - (a, b) => assert(a.toBigInt() === b, 'equal results') - ); - // inside provable code - Provable.runAndCheck(() => { - x = Provable.witness(Field, () => x); - y = Provable.witness(Field, () => y); - handleErrors( - () => op1(x, y), - () => op2(x0, y0), - (a, b) => - Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) - ); - handleErrors( - () => op1(x, y0), - () => op2(x0, y0), - (a, b) => - Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) - ); - }); - }); -} -function equivalentVoid1( - op1: (x: Field) => void, - op2: (x: bigint) => void, - rng: Random = Random.field -) { - test(rng, (x0) => { - let x = Field(x0); - // outside provable code - handleErrors( - () => op1(x), - () => op2(x0) - ); - // inside provable code - Provable.runAndCheck(() => { - x = Provable.witness(Field, () => x); - handleErrors( - () => op1(x), - () => op2(x0) - ); - }); - }); -} -function equivalentVoid2( - op1: (x: Field, y: Field | bigint) => void, - op2: (x: bigint, y: bigint) => void, - rng: Random = Random.field -) { - test(rng, rng, (x0, y0) => { - let x = Field(x0); - let y = Field(y0); - // outside provable code - handleErrors( - () => op1(x, y), - () => op2(x0, y0) - ); - handleErrors( - () => op1(x, y0), - () => op2(x0, y0) - ); - // inside provable code - Provable.runAndCheck(() => { - x = Provable.witness(Field, () => x); - y = Provable.witness(Field, () => y); - handleErrors( - () => op1(x, y), - () => op2(x0, y0) - ); - handleErrors( - () => op1(x, y0), - () => op2(x0, y0) - ); - }); - }); -} - -function handleErrors( - op1: () => T, - op2: () => S, - useResults?: (a: T, b: S) => R -): R | undefined { - let result1: T, result2: S; - let error1: Error | undefined; - let error2: Error | undefined; - try { - result1 = op1(); - } catch (err) { - error1 = err as Error; - } - try { - result2 = op2(); - } catch (err) { - error2 = err as Error; - } - if (!!error1 !== !!error2) { - error1 && console.log(error1); - error2 && console.log(error2); - } - deepEqual(!!error1, !!error2, 'equivalent errors'); - if (!(error1 || error2) && useResults !== undefined) { - return useResults(result1!, result2!); - } -} - -function throwError(message?: string): any { - throw Error(message); -} diff --git a/src/lib/group.test.ts b/src/lib/group.test.ts deleted file mode 100644 index 64faa9b93e..0000000000 --- a/src/lib/group.test.ts +++ /dev/null @@ -1,416 +0,0 @@ -import { Bool, Group, Scalar, Provable } from 'snarkyjs'; - -describe('group', () => { - let g = Group({ - x: -1, - y: 2, - }); - - describe('Inside circuit', () => { - describe('group membership', () => { - it('valid element does not throw', () => { - expect(() => { - Provable.runAndCheck(() => { - Provable.witness(Group, () => g); - }); - }).not.toThrow(); - }); - - it('valid element does not throw', () => { - expect(() => { - Provable.runAndCheck(() => { - Provable.witness(Group, () => Group.generator); - }); - }).not.toThrow(); - }); - - it('Group.zero element does not throw', () => { - expect(() => { - Provable.runAndCheck(() => { - Provable.witness(Group, () => Group.zero); - }); - }).not.toThrow(); - }); - - it('invalid group element throws', () => { - expect(() => { - Provable.runAndCheck(() => { - Provable.witness(Group, () => Group({ x: 2, y: 2 })); - }); - }).toThrow(); - }); - }); - - describe('add', () => { - it('g+g does not throw', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => g); - const y = Provable.witness(Group, () => g); - x.add(y); - }); - }).not.toThrow(); - }); - - it('g+zero = g', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => g); - const zero = Provable.witness(Group, () => Group.zero); - x.add(zero).assertEquals(x); - }); - }).not.toThrow(); - }); - - it('zero+g = g', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => g); - const zero = Provable.witness(Group, () => Group.zero); - zero.add(x).assertEquals(x); - }); - }).not.toThrow(); - }); - - it('g+(-g) = zero', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => g); - const zero = Provable.witness(Group, () => Group.zero); - x.add(x.neg()).assertEquals(zero); - }); - }).not.toThrow(); - }); - - it('(-g)+g = zero', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => g); - const zero = Provable.witness(Group, () => Group.zero); - x.neg().add(x).assertEquals(zero); - }); - }).not.toThrow(); - }); - - it('zero + zero = zero', () => { - expect(() => { - Provable.runAndCheck(() => { - const zero = Provable.witness(Group, () => Group.zero); - zero.add(zero).assertEquals(zero); - }); - }).not.toThrow(); - }); - }); - - describe('sub', () => { - it('g-g does not throw', () => { - expect(() => { - Provable.runAndCheck(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => g); - const y = Provable.witness(Group, () => g); - x.sub(y); - }); - }); - }).not.toThrow(); - }); - - it('g-zero = g', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => g); - const zero = Provable.witness(Group, () => Group.zero); - x.sub(zero).assertEquals(x); - }); - }).not.toThrow(); - }); - - it('zero - g = -g', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => g); - const zero = Provable.witness(Group, () => Group.zero); - zero.sub(x).assertEquals(x.neg()); - }); - }).not.toThrow(); - }); - - it('zero - zero = zero', () => { - expect(() => { - Provable.runAndCheck(() => { - const zero = Provable.witness(Group, () => Group.zero); - zero.sub(zero).assertEquals(zero); - }); - }).not.toThrow(); - }); - }); - - describe('neg', () => { - it('neg(g) not to throw', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => g); - x.neg(); - }); - }).not.toThrow(); - }); - - it('neg(zero) = zero', () => { - expect(() => { - Provable.runAndCheck(() => { - const zero = Provable.witness(Group, () => Group.zero); - zero.neg().assertEquals(zero); - }); - }).not.toThrow(); - }); - }); - - describe('scale', () => { - it('scaling with random Scalar does not throw', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => g); - x.scale(Scalar.random()); - }); - }).not.toThrow(); - }); - - it('x*g+y*g = (x+y)*g', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Scalar.from(2); - const y = Scalar.from(3); - const left = g.scale(x).add(g.scale(y)); - const right = g.scale(x.add(y)); - left.assertEquals(right); - }); - }).not.toThrow(); - }); - - it('x*(y*g) = (x*y)*g', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Scalar.from(2); - const y = Scalar.from(3); - const left = g.scale(y).scale(x); - const right = g.scale(y.mul(x)); - left.assertEquals(right); - }); - }).not.toThrow(); - }); - }); - - describe('equals', () => { - it('should equal true with same group', () => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => Group.generator); - let isEqual = x.equals(Group.generator); - Provable.asProver(() => { - expect(isEqual.toBoolean()).toEqual(true); - }); - }); - }); - - it('should equal false with different group', () => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => Group.generator); - let isEqual = x.equals(g); - Provable.asProver(() => { - expect(isEqual.toBoolean()).toEqual(false); - }); - }); - }); - }); - - describe('assertEquals', () => { - it('should not throw with same group', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => Group.generator); - x.assertEquals(Group.generator); - }); - }).not.toThrow(); - }); - - it('should throw with different group', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Group, () => Group.generator); - x.assertEquals(g); - }); - }).toThrow(); - }); - }); - - describe('toJSON', () => { - it('fromJSON(g.toJSON) should be the same as g', () => { - Provable.runAndCheck(() => { - const x = Provable.witness( - Group, - () => Group.fromJSON(Group.generator.toJSON())! - ); - Provable.asProver(() => { - expect(x.equals(Group.generator).toBoolean()).toEqual(true); - }); - }); - }); - }); - }); - - describe('Outside circuit', () => { - describe('neg', () => { - it('neg not to throw', () => { - expect(() => { - g.neg(); - }).not.toThrow(); - }); - - it('zero.neg = zero', () => { - expect(() => { - const zero = Group.zero; - zero.neg().assertEquals(zero); - }).not.toThrow(); - }); - }); - - describe('add', () => { - it('(-1,2)+(-1,2) does not throw', () => { - expect(() => { - g.add(g); - }).not.toThrow(); - }); - - it('g + zero = g', () => { - expect(() => { - const zero = Group.zero; - g.add(zero).assertEquals(g); - }).not.toThrow(); - }); - - it('zero + g = g', () => { - expect(() => { - const zero = Group.zero; - zero.add(g).assertEquals(g); - }).not.toThrow(); - }); - - it('g + (-g) = zero', () => { - expect(() => { - const zero = Group.zero; - g.add(g.neg()).assertEquals(zero); - }).not.toThrow(); - }); - - it('(-g) + g = zero', () => { - expect(() => { - const zero = Group.zero; - g.neg().add(g).assertEquals(zero); - }).not.toThrow(); - }); - - it('zero + zero = zero', () => { - expect(() => { - const zero = Group.zero; - zero.add(zero).assertEquals(zero); - }).not.toThrow(); - }); - }); - - describe('sub', () => { - it('generator-(-1,2) does not throw', () => { - expect(() => { - Group.generator.sub(g); - }).not.toThrow(); - }); - - it('g - zero = g', () => { - expect(() => { - const zero = Group.zero; - g.sub(zero).assertEquals(g); - }).not.toThrow(); - }); - - it('zero - g = -g', () => { - expect(() => { - const zero = Group.zero; - zero.sub(g).assertEquals(g.neg()); - }).not.toThrow(); - }); - - it('zero - zero = -zero', () => { - expect(() => { - const zero = Group.zero; - zero.sub(zero).assertEquals(zero); - }).not.toThrow(); - }); - }); - - describe('scale', () => { - it('scaling with random Scalar does not throw', () => { - expect(() => { - g.scale(Scalar.random()); - }).not.toThrow(); - }); - - it('x*g+y*g = (x+y)*g', () => { - const x = Scalar.from(2); - const y = Scalar.from(3); - const left = g.scale(x).add(g.scale(y)); - const right = g.scale(x.add(y)); - expect(left).toEqual(right); - }); - - it('x*(y*g) = (x*y)*g', () => { - const x = Scalar.from(2); - const y = Scalar.from(3); - const left = g.scale(y).scale(x); - const right = g.scale(y.mul(x)); - expect(left).toEqual(right); - }); - }); - - describe('equals', () => { - it('should equal true with same group', () => { - expect(g.equals(g)).toEqual(Bool(true)); - }); - - it('should equal false with different group', () => { - expect(g.equals(Group.generator)).toEqual(Bool(false)); - }); - }); - - describe('toJSON', () => { - it("fromJSON('1','1') should be the same as Group(1,1)", () => { - const x = Group.fromJSON({ x: -1, y: 2 }); - expect(x).toEqual(g); - }); - }); - }); - - describe('Variable/Constant circuit equality ', () => { - it('add', () => { - Provable.runAndCheck(() => { - let y = Provable.witness(Group, () => g).add( - Provable.witness(Group, () => Group.generator) - ); - let z = g.add(Group.generator); - y.assertEquals(z); - }); - }); - - it('sub', () => { - let y = Provable.witness(Group, () => g).sub( - Provable.witness(Group, () => Group.generator) - ); - let z = g.sub(Group.generator); - y.assertEquals(z); - }); - - it('sub', () => { - let y = Provable.witness(Group, () => g).assertEquals( - Provable.witness(Group, () => g) - ); - g.assertEquals(g); - }); - }); -}); diff --git a/src/lib/int.test.ts b/src/lib/int.test.ts deleted file mode 100644 index e75c5a679f..0000000000 --- a/src/lib/int.test.ts +++ /dev/null @@ -1,2153 +0,0 @@ -import { - isReady, - Provable, - shutdown, - Int64, - UInt64, - UInt32, - Field, - Bool, - Sign, -} from 'snarkyjs'; - -describe('int', () => { - beforeAll(async () => { - await isReady; - }); - - afterAll(async () => { - // Use a timeout to defer the execution of `shutdown()` until Jest processes all tests. - // `shutdown()` exits the process when it's done cleanup so we want to delay it's execution until Jest is done - setTimeout(async () => { - await shutdown(); - }, 0); - }); - - const NUMBERMAX = 2 ** 53 - 1; // JavaScript numbers can only safely store integers in the range -(2^53 − 1) to 2^53 − 1 - - describe('Int64', () => { - describe('toString', () => { - it('should be the same as Field(0)', async () => { - const int = new Int64(UInt64.zero, Sign.one); - const field = Field(0); - expect(int.toString()).toEqual(field.toString()); - }); - - it('should be -1', async () => { - const int = new Int64(UInt64.one).neg(); - expect(int.toString()).toEqual('-1'); - }); - - it('should be the same as 2^53-1', async () => { - const int = Int64.fromField(Field(String(NUMBERMAX))); - const field = Field(String(NUMBERMAX)); - expect(int.toString()).toEqual(field.toString()); - }); - }); - - describe('zero', () => { - it('should be the same as Field(0)', async () => { - expect(Int64.zero.magnitude.value).toEqual(Field(0)); - }); - }); - - describe('fromUnsigned', () => { - it('should be the same as UInt64.zero', async () => { - expect(new Int64(UInt64.zero, Sign.one)).toEqual( - Int64.fromUnsigned(UInt64.zero) - ); - }); - - it('should be the same as UInt64.MAXINT', async () => { - expect(Int64.from((1n << 64n) - 1n)).toEqual( - Int64.fromUnsigned(UInt64.MAXINT()) - ); - }); - }); - - describe('neg', () => { - it('neg(1)=-1', () => { - const int = Int64.one; - expect(int.neg().toField()).toEqual(Field(-1)); - }); - it('neg(2^53-1)=-2^53-1', () => { - const int = Int64.from(NUMBERMAX); - expect(int.neg().toString()).toEqual(`${-NUMBERMAX}`); - }); - }); - - describe('add', () => { - it('1+1=2', () => { - expect(Int64.one.add(Int64.from('1')).toString()).toEqual('2'); - }); - - it('5000+(-4000)=1000', () => { - expect( - Int64.from(5000) - .add(Int64.fromField(Field(-4000))) - .toString() - ).toEqual('1000'); - }); - - it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { - const value = ((1n << 64n) - 2n) / 2n; - expect( - Int64.from(value).add(Int64.from(value)).add(Int64.one).toString() - ).toEqual(UInt64.MAXINT().toString()); - }); - - it('should throw on overflow', () => { - expect(() => { - Int64.from(1n << 64n); - }).toThrow(); - expect(() => { - Int64.from(-(1n << 64n)); - }).toThrow(); - expect(() => { - Int64.from(100).add(1n << 64n); - }).toThrow(); - expect(() => { - Int64.from(100).sub('1180591620717411303424'); - }).toThrow(); - expect(() => { - Int64.from(100).mul(UInt64.from(Field(1n << 100n))); - }).toThrow(); - }); - - // TODO - should we make these throw? - // These are edge cases, where one of two inputs is out of the Int64 range, - // but the result of an operation with a proper Int64 moves it into the range. - // They would only get caught if we'd also check the range in the Int64 / UInt64 constructors, - // which breaks out current practice of having a dumb constructor that only stores variables - it.skip('operations should throw on overflow of any input', () => { - expect(() => { - new Int64(new UInt64(Field(1n << 64n))).sub(1); - }).toThrow(); - expect(() => { - new Int64(new UInt64(Field(-(1n << 64n)))).add(5); - }).toThrow(); - expect(() => { - Int64.from(20).sub(new UInt64(Field((1n << 64n) + 10n))); - }).toThrow(); - expect(() => { - Int64.from(6).add(new UInt64(Field(-(1n << 64n) - 5n))); - }).toThrow(); - }); - - it('should throw on overflow addition', () => { - expect(() => { - Int64.from((1n << 64n) - 1n).add(1); - }).toThrow(); - expect(() => { - Int64.one.add((1n << 64n) - 1n); - }).toThrow(); - }); - it('should not throw on non-overflowing addition', () => { - expect(() => { - Int64.from((1n << 64n) - 1n).add(Int64.zero); - }).not.toThrow(); - }); - }); - - describe('sub', () => { - it('1-1=0', () => { - expect(Int64.one.sub(Int64.from(1)).toString()).toEqual('0'); - }); - - it('10000-5000=5000', () => { - expect( - Int64.fromField(Field(10000)).sub(Int64.from('5000')).toString() - ).toEqual('5000'); - }); - - it('0-1=-1', () => { - expect(Int64.zero.sub(Int64.one).toString()).toEqual('-1'); - }); - - it('(0-MAXINT) subs to -MAXINT', () => { - expect(Int64.zero.sub(UInt64.MAXINT()).toString()).toEqual( - '-' + UInt64.MAXINT().toString() - ); - }); - }); - - describe('toFields', () => { - it('toFields(1) should be the same as [Field(1), Field(1)]', () => { - expect(Int64.toFields(Int64.one)).toEqual([Field(1), Field(1)]); - }); - - it('toFields(2^53-1) should be the same as Field(2^53-1)', () => { - expect(Int64.toFields(Int64.from(NUMBERMAX))).toEqual([ - Field(String(NUMBERMAX)), - Field(1), - ]); - }); - }); - describe('fromFields', () => { - it('fromFields([1, 1]) should be the same as Int64.one', () => { - expect(Int64.fromFields([Field(1), Field(1)])).toEqual(Int64.one); - }); - - it('fromFields(2^53-1) should be the same as Field(2^53-1)', () => { - expect(Int64.fromFields([Field(String(NUMBERMAX)), Field(1)])).toEqual( - Int64.from(NUMBERMAX) - ); - }); - }); - - describe('mul / div / mod', () => { - it('mul, div and mod work', () => { - // 2 ** 6 === 64 - let x = Int64.fromField(Field(2)) - .mul(2) - .mul('2') - .mul(2n) - .mul(UInt32.from(2)) - .mul(UInt64.from(2)); - expect(`${x}`).toBe('64'); - - // 64 * (-64) === -64**2 - let y = Int64.from(-64); - expect(`${x.mul(y)}`).toEqual(`${-(64 ** 2)}`); - // (-64) // 64 === -1 - expect(y.div(x).toString()).toEqual('-1'); - // (-64) // 65 === 0 - expect(y.div(65).toString()).toEqual('0'); - // 64 % 3 === 1 - expect(x.mod(3).toString()).toEqual('1'); - // (-64) % 3 === 2 - expect(y.mod(3).toString()).toEqual('2'); - }); - }); - }); - - describe('UInt64', () => { - describe('Inside circuit', () => { - describe('add', () => { - it('1+1=2', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.add(y).assertEquals(new UInt64(Field(2))); - }); - }).not.toThrow(); - }); - - it('5000+5000=10000', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(5000))); - const y = Provable.witness(UInt64, () => new UInt64(Field(5000))); - x.add(y).assertEquals(new UInt64(Field(10000))); - }); - }).not.toThrow(); - }); - - it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { - const n = Field((((1n << 64n) - 2n) / 2n).toString()); - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(n)); - const y = Provable.witness(UInt64, () => new UInt64(n)); - x.add(y).add(1).assertEquals(UInt64.MAXINT()); - }); - }).not.toThrow(); - }); - - it('should throw on overflow addition', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => UInt64.MAXINT()); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.add(y); - }); - }).toThrow(); - }); - }); - - describe('sub', () => { - it('1-1=0', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.sub(y).assertEquals(new UInt64(Field(0))); - }); - }).not.toThrow(); - }); - - it('10000-5000=5000', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness( - UInt64, - () => new UInt64(Field(10000)) - ); - const y = Provable.witness(UInt64, () => new UInt64(Field(5000))); - x.sub(y).assertEquals(new UInt64(Field(5000))); - }); - }).not.toThrow(); - }); - - it('should throw on sub if results in negative number', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(0))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.sub(y); - }); - }).toThrow(); - }); - }); - - describe('mul', () => { - it('1x2=2', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1))); - const y = Provable.witness(UInt64, () => new UInt64(Field(2))); - x.mul(y).assertEquals(new UInt64(Field(2))); - }); - }).not.toThrow(); - }); - - it('1x0=0', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1))); - const y = Provable.witness(UInt64, () => new UInt64(Field(0))); - x.mul(y).assertEquals(new UInt64(Field(0))); - }); - }).not.toThrow(); - }); - - it('1000x1000=1000000', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1000))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1000))); - x.mul(y).assertEquals(new UInt64(Field(1000000))); - }); - }).not.toThrow(); - }); - - it('MAXINTx1=MAXINT', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => UInt64.MAXINT()); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.mul(y).assertEquals(UInt64.MAXINT()); - }); - }).not.toThrow(); - }); - - it('should throw on overflow multiplication', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => UInt64.MAXINT()); - const y = Provable.witness(UInt64, () => new UInt64(Field(2))); - x.mul(y); - }); - }).toThrow(); - }); - }); - - describe('div', () => { - it('2/1=2', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(2))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.div(y).assertEquals(new UInt64(Field(2))); - }); - }).not.toThrow(); - }); - - it('0/1=0', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(0))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.div(y).assertEquals(new UInt64(Field(0))); - }); - }).not.toThrow(); - }); - - it('2000/1000=2', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(2000))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1000))); - x.div(y).assertEquals(new UInt64(Field(2))); - }); - }).not.toThrow(); - }); - - it('MAXINT/1=MAXINT', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => UInt64.MAXINT()); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.div(y).assertEquals(UInt64.MAXINT()); - }); - }).not.toThrow(); - }); - - it('should throw on division by zero', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => UInt64.MAXINT()); - const y = Provable.witness(UInt64, () => new UInt64(Field(0))); - x.div(y); - }); - }).toThrow(); - }); - }); - - describe('mod', () => { - it('1%1=0', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.mod(y).assertEquals(new UInt64(Field(0))); - }); - }).not.toThrow(); - }); - - it('500%32=20', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(500))); - const y = Provable.witness(UInt64, () => new UInt64(Field(32))); - x.mod(y).assertEquals(new UInt64(Field(20))); - }); - }).not.toThrow(); - }); - - it('MAXINT%7=1', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => UInt64.MAXINT()); - const y = Provable.witness(UInt64, () => new UInt64(Field(7))); - x.mod(y).assertEquals(new UInt64(Field(1))); - }); - }).not.toThrow(); - }); - - it('should throw on mod by zero', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => UInt64.MAXINT()); - const y = Provable.witness(UInt64, () => new UInt64(Field(0))); - x.mod(y).assertEquals(new UInt64(Field(1))); - }); - }).toThrow(); - }); - }); - - describe('assertLt', () => { - it('1<2=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1))); - const y = Provable.witness(UInt64, () => new UInt64(Field(2))); - x.assertLessThan(y); - }); - }).not.toThrow(); - }); - - it('1<1=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.assertLessThan(y); - }); - }).toThrow(); - }); - - it('2<1=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(2))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.assertLessThan(y); - }); - }).toThrow(); - }); - - it('1000<100000=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1000))); - const y = Provable.witness( - UInt64, - () => new UInt64(Field(100000)) - ); - x.assertLessThan(y); - }); - }).not.toThrow(); - }); - - it('100000<1000=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness( - UInt64, - () => new UInt64(Field(100000)) - ); - const y = Provable.witness(UInt64, () => new UInt64(Field(1000))); - x.assertLessThan(y); - }); - }).toThrow(); - }); - - it('MAXINT { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => UInt64.MAXINT()); - const y = Provable.witness(UInt64, () => UInt64.MAXINT()); - x.assertLessThan(y); - }); - }).toThrow(); - }); - }); - - describe('assertLte', () => { - it('1<=1=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.assertLessThanOrEqual(y); - }); - }).not.toThrow(); - }); - - it('2<=1=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(2))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.assertLessThanOrEqual(y); - }); - }).toThrow(); - }); - - it('1000<=100000=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1000))); - const y = Provable.witness( - UInt64, - () => new UInt64(Field(100000)) - ); - x.assertLessThanOrEqual(y); - }); - }).not.toThrow(); - }); - - it('100000<=1000=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness( - UInt64, - () => new UInt64(Field(100000)) - ); - const y = Provable.witness(UInt64, () => new UInt64(Field(1000))); - x.assertLessThanOrEqual(y); - }); - }).toThrow(); - }); - - it('MAXINT<=MAXINT=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => UInt64.MAXINT()); - const y = Provable.witness(UInt64, () => UInt64.MAXINT()); - x.assertLessThanOrEqual(y); - }); - }).not.toThrow(); - }); - }); - - describe('assertGreaterThan', () => { - it('2>1=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(2))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.assertGreaterThan(y); - }); - }).not.toThrow(); - }); - - it('1>1=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.assertGreaterThan(y); - }); - }).toThrow(); - }); - - it('1>2=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1))); - const y = Provable.witness(UInt64, () => new UInt64(Field(2))); - x.assertGreaterThan(y); - }); - }).toThrow(); - }); - - it('100000>1000=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness( - UInt64, - () => new UInt64(Field(100000)) - ); - const y = Provable.witness(UInt64, () => new UInt64(Field(1000))); - x.assertGreaterThan(y); - }); - }).not.toThrow(); - }); - - it('1000>100000=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1000))); - const y = Provable.witness( - UInt64, - () => new UInt64(Field(100000)) - ); - x.assertGreaterThan(y); - }); - }).toThrow(); - }); - - it('MAXINT>MAXINT=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => UInt64.MAXINT()); - const y = Provable.witness(UInt64, () => UInt64.MAXINT()); - x.assertGreaterThan(y); - }); - }).toThrow(); - }); - }); - - describe('assertGreaterThanOrEqual', () => { - it('1<=1=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1))); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.assertGreaterThanOrEqual(y); - }); - }).not.toThrow(); - }); - - it('1>=2=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1))); - const y = Provable.witness(UInt64, () => new UInt64(Field(2))); - x.assertGreaterThanOrEqual(y); - }); - }).toThrow(); - }); - - it('100000>=1000=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness( - UInt64, - () => new UInt64(Field(100000)) - ); - const y = Provable.witness(UInt64, () => new UInt64(Field(1000))); - x.assertGreaterThanOrEqual(y); - }); - }).not.toThrow(); - }); - - it('1000>=100000=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => new UInt64(Field(1000))); - const y = Provable.witness( - UInt64, - () => new UInt64(Field(100000)) - ); - x.assertGreaterThanOrEqual(y); - }); - }).toThrow(); - }); - - it('MAXINT>=MAXINT=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => UInt64.MAXINT()); - const y = Provable.witness(UInt64, () => UInt64.MAXINT()); - x.assertGreaterThanOrEqual(y); - }); - }).not.toThrow(); - }); - }); - - describe('from() ', () => { - describe('fromNumber()', () => { - it('should be the same as Field(1)', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => UInt64.from(1)); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.assertEquals(y); - }); - }).not.toThrow(); - }); - - it('should be the same as 2^53-1', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => - UInt64.from(NUMBERMAX) - ); - const y = Provable.witness( - UInt64, - () => new UInt64(Field(String(NUMBERMAX))) - ); - x.assertEquals(y); - }); - }).not.toThrow(); - }); - }); - describe('fromString()', () => { - it('should be the same as Field(1)', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => UInt64.from('1')); - const y = Provable.witness(UInt64, () => new UInt64(Field(1))); - x.assertEquals(y); - }); - }).not.toThrow(); - }); - - it('should be the same as 2^53-1', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt64, () => - UInt64.from(String(NUMBERMAX)) - ); - const y = Provable.witness( - UInt64, - () => new UInt64(Field(String(NUMBERMAX))) - ); - x.assertEquals(y); - }); - }).not.toThrow(); - }); - }); - }); - }); - - describe('Outside of circuit', () => { - describe('add', () => { - it('1+1=2', () => { - expect(new UInt64(Field(1)).add(1).toString()).toEqual('2'); - }); - - it('5000+5000=10000', () => { - expect(new UInt64(Field(5000)).add(5000).toString()).toEqual('10000'); - }); - - it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { - const value = Field((((1n << 64n) - 2n) / 2n).toString()); - expect( - new UInt64(value) - .add(new UInt64(value)) - .add(new UInt64(Field(1))) - .toString() - ).toEqual(UInt64.MAXINT().toString()); - }); - - it('should throw on overflow addition', () => { - expect(() => { - UInt64.MAXINT().add(1); - }).toThrow(); - }); - }); - - describe('sub', () => { - it('1-1=0', () => { - expect(new UInt64(Field(1)).sub(1).toString()).toEqual('0'); - }); - - it('10000-5000=5000', () => { - expect(new UInt64(Field(10000)).sub(5000).toString()).toEqual('5000'); - }); - - it('should throw on sub if results in negative number', () => { - expect(() => { - UInt64.from(0).sub(1); - }).toThrow(); - }); - }); - - describe('mul', () => { - it('1x2=2', () => { - expect(new UInt64(Field(1)).mul(2).toString()).toEqual('2'); - }); - - it('1x0=0', () => { - expect(new UInt64(Field(1)).mul(0).toString()).toEqual('0'); - }); - - it('1000x1000=1000000', () => { - expect(new UInt64(Field(1000)).mul(1000).toString()).toEqual( - '1000000' - ); - }); - - it('MAXINTx1=MAXINT', () => { - expect(UInt64.MAXINT().mul(1).toString()).toEqual( - UInt64.MAXINT().toString() - ); - }); - - it('should throw on overflow multiplication', () => { - expect(() => { - UInt64.MAXINT().mul(2); - }).toThrow(); - }); - }); - - describe('div', () => { - it('2/1=2', () => { - expect(new UInt64(Field(2)).div(1).toString()).toEqual('2'); - }); - - it('0/1=0', () => { - expect(new UInt64(Field(0)).div(1).toString()).toEqual('0'); - }); - - it('2000/1000=2', () => { - expect(new UInt64(Field(2000)).div(1000).toString()).toEqual('2'); - }); - - it('MAXINT/1=MAXINT', () => { - expect(UInt64.MAXINT().div(1).toString()).toEqual( - UInt64.MAXINT().toString() - ); - }); - - it('should throw on division by zero', () => { - expect(() => { - UInt64.MAXINT().div(0); - }).toThrow(); - }); - }); - - describe('mod', () => { - it('1%1=0', () => { - expect(new UInt64(Field(1)).mod(1).toString()).toEqual('0'); - }); - - it('500%32=20', () => { - expect(new UInt64(Field(500)).mod(32).toString()).toEqual('20'); - }); - - it('MAXINT%7=1', () => { - expect(UInt64.MAXINT().mod(7).toString()).toEqual('1'); - }); - - it('should throw on mod by zero', () => { - expect(() => { - UInt64.MAXINT().mod(0); - }).toThrow(); - }); - }); - - describe('lt', () => { - it('1<2=true', () => { - expect(new UInt64(Field(1)).lessThan(new UInt64(Field(2)))).toEqual( - Bool(true) - ); - }); - - it('1<1=false', () => { - expect(new UInt64(Field(1)).lessThan(new UInt64(Field(1)))).toEqual( - Bool(false) - ); - }); - - it('2<1=false', () => { - expect(new UInt64(Field(2)).lessThan(new UInt64(Field(1)))).toEqual( - Bool(false) - ); - }); - - it('1000<100000=true', () => { - expect( - new UInt64(Field(1000)).lessThan(new UInt64(Field(100000))) - ).toEqual(Bool(true)); - }); - - it('100000<1000=false', () => { - expect( - new UInt64(Field(100000)).lessThan(new UInt64(Field(1000))) - ).toEqual(Bool(false)); - }); - - it('MAXINT { - expect(UInt64.MAXINT().lessThan(UInt64.MAXINT())).toEqual( - Bool(false) - ); - }); - }); - - describe('lte', () => { - it('1<=1=true', () => { - expect( - new UInt64(Field(1)).lessThanOrEqual(new UInt64(Field(1))) - ).toEqual(Bool(true)); - }); - - it('2<=1=false', () => { - expect( - new UInt64(Field(2)).lessThanOrEqual(new UInt64(Field(1))) - ).toEqual(Bool(false)); - }); - - it('1000<=100000=true', () => { - expect( - new UInt64(Field(1000)).lessThanOrEqual(new UInt64(Field(100000))) - ).toEqual(Bool(true)); - }); - - it('100000<=1000=false', () => { - expect( - new UInt64(Field(100000)).lessThanOrEqual(new UInt64(Field(1000))) - ).toEqual(Bool(false)); - }); - - it('MAXINT<=MAXINT=true', () => { - expect(UInt64.MAXINT().lessThanOrEqual(UInt64.MAXINT())).toEqual( - Bool(true) - ); - }); - }); - - describe('assertLessThanOrEqual', () => { - it('1<=1=true', () => { - expect(() => { - new UInt64(Field(1)).assertLessThanOrEqual(new UInt64(Field(1))); - }).not.toThrow(); - }); - - it('2<=1=false', () => { - expect(() => { - new UInt64(Field(2)).assertLessThanOrEqual(new UInt64(Field(1))); - }).toThrow(); - }); - - it('1000<=100000=true', () => { - expect(() => { - new UInt64(Field(1000)).assertLessThanOrEqual( - new UInt64(Field(100000)) - ); - }).not.toThrow(); - }); - - it('100000<=1000=false', () => { - expect(() => { - new UInt64(Field(100000)).assertLessThanOrEqual( - new UInt64(Field(1000)) - ); - }).toThrow(); - }); - - it('MAXINT<=MAXINT=true', () => { - expect(() => { - UInt64.MAXINT().assertLessThanOrEqual(UInt64.MAXINT()); - }).not.toThrow(); - }); - }); - - describe('greaterThan', () => { - it('2>1=true', () => { - expect( - new UInt64(Field(2)).greaterThan(new UInt64(Field(1))) - ).toEqual(Bool(true)); - }); - - it('1>1=false', () => { - expect( - new UInt64(Field(1)).greaterThan(new UInt64(Field(1))) - ).toEqual(Bool(false)); - }); - - it('1>2=false', () => { - expect( - new UInt64(Field(1)).greaterThan(new UInt64(Field(2))) - ).toEqual(Bool(false)); - }); - - it('100000>1000=true', () => { - expect( - new UInt64(Field(100000)).greaterThan(new UInt64(Field(1000))) - ).toEqual(Bool(true)); - }); - - it('1000>100000=false', () => { - expect( - new UInt64(Field(1000)).greaterThan(new UInt64(Field(100000))) - ).toEqual(Bool(false)); - }); - - it('MAXINT>MAXINT=false', () => { - expect(UInt64.MAXINT().greaterThan(UInt64.MAXINT())).toEqual( - Bool(false) - ); - }); - }); - - describe('greaterThanOrEqual', () => { - it('2>=1=true', () => { - expect( - new UInt64(Field(2)).greaterThanOrEqual(new UInt64(Field(1))) - ).toEqual(Bool(true)); - }); - - it('1>=1=true', () => { - expect( - new UInt64(Field(1)).greaterThanOrEqual(new UInt64(Field(1))) - ).toEqual(Bool(true)); - }); - - it('1>=2=false', () => { - expect( - new UInt64(Field(1)).greaterThanOrEqual(new UInt64(Field(2))) - ).toEqual(Bool(false)); - }); - - it('100000>=1000=true', () => { - expect( - new UInt64(Field(100000)).greaterThanOrEqual( - new UInt64(Field(1000)) - ) - ).toEqual(Bool(true)); - }); - - it('1000>=100000=false', () => { - expect( - new UInt64(Field(1000)).greaterThanOrEqual( - new UInt64(Field(100000)) - ) - ).toEqual(Bool(false)); - }); - - it('MAXINT>=MAXINT=true', () => { - expect(UInt64.MAXINT().greaterThanOrEqual(UInt64.MAXINT())).toEqual( - Bool(true) - ); - }); - }); - - describe('assertGreaterThan', () => { - it('1>1=false', () => { - expect(() => { - new UInt64(Field(1)).assertGreaterThan(new UInt64(Field(1))); - }).toThrow(); - }); - - it('2>1=true', () => { - expect(() => { - new UInt64(Field(2)).assertGreaterThan(new UInt64(Field(1))); - }).not.toThrow(); - }); - - it('1000>100000=false', () => { - expect(() => { - new UInt64(Field(1000)).assertGreaterThan( - new UInt64(Field(100000)) - ); - }).toThrow(); - }); - - it('100000>1000=true', () => { - expect(() => { - new UInt64(Field(100000)).assertGreaterThan( - new UInt64(Field(1000)) - ); - }).not.toThrow(); - }); - - it('MAXINT>MAXINT=false', () => { - expect(() => { - UInt64.MAXINT().assertGreaterThan(UInt64.MAXINT()); - }).toThrow(); - }); - }); - - describe('assertGreaterThanOrEqual', () => { - it('1>=1=true', () => { - expect(() => { - new UInt64(Field(1)).assertGreaterThanOrEqual(new UInt64(Field(1))); - }).not.toThrow(); - }); - - it('2>=1=true', () => { - expect(() => { - new UInt64(Field(2)).assertGreaterThanOrEqual(new UInt64(Field(1))); - }).not.toThrow(); - }); - - it('1000>=100000=false', () => { - expect(() => { - new UInt64(Field(1000)).assertGreaterThanOrEqual( - new UInt64(Field(100000)) - ); - }).toThrow(); - }); - - it('100000>=1000=true', () => { - expect(() => { - new UInt64(Field(100000)).assertGreaterThanOrEqual( - new UInt64(Field(1000)) - ); - }).not.toThrow(); - }); - - it('MAXINT>=MAXINT=true', () => { - expect(() => { - UInt64.MAXINT().assertGreaterThanOrEqual(UInt64.MAXINT()); - }).not.toThrow(); - }); - }); - - describe('toString()', () => { - it('should be the same as Field(0)', async () => { - const uint64 = new UInt64(Field(0)); - const field = Field(0); - expect(uint64.toString()).toEqual(field.toString()); - }); - it('should be the same as 2^53-1', async () => { - const uint64 = new UInt64(Field(String(NUMBERMAX))); - const field = Field(String(NUMBERMAX)); - expect(uint64.toString()).toEqual(field.toString()); - }); - }); - - describe('check()', () => { - it('should pass checking a MAXINT', () => { - expect(() => { - UInt64.check(UInt64.MAXINT()); - }).not.toThrow(); - }); - - it('should throw checking over MAXINT', () => { - const aboveMax = new UInt64(Field((1n << 64n).toString())); // This number is defined in UInt64.MAXINT() - expect(() => { - UInt64.check(aboveMax); - }).toThrow(); - }); - }); - - describe('from() ', () => { - describe('fromNumber()', () => { - it('should be the same as Field(1)', () => { - const uint = UInt64.from(1); - expect(uint.value).toEqual(new UInt64(Field(1)).value); - }); - - it('should be the same as 2^53-1', () => { - const uint = UInt64.from(NUMBERMAX); - expect(uint.value).toEqual(Field(String(NUMBERMAX))); - }); - }); - describe('fromString()', () => { - it('should be the same as Field(1)', () => { - const uint = UInt64.from('1'); - expect(uint.value).toEqual(new UInt64(Field(1)).value); - }); - - it('should be the same as 2^53-1', () => { - const uint = UInt64.from(String(NUMBERMAX)); - expect(uint.value).toEqual(Field(String(NUMBERMAX))); - }); - }); - }); - }); - }); - - describe('UInt32', () => { - const NUMBERMAX = 2 ** 32 - 1; - - describe('Inside circuit', () => { - describe('add', () => { - it('1+1=2', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.add(y).assertEquals(new UInt32(Field(2))); - }); - }).not.toThrow(); - }); - - it('5000+5000=10000', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(5000))); - const y = Provable.witness(UInt32, () => new UInt32(Field(5000))); - x.add(y).assertEquals(new UInt32(Field(10000))); - }); - }).not.toThrow(); - }); - - it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { - const n = Field((((1n << 32n) - 2n) / 2n).toString()); - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(n)); - const y = Provable.witness(UInt32, () => new UInt32(n)); - x.add(y).add(1).assertEquals(UInt32.MAXINT()); - }); - }).not.toThrow(); - }); - - it('should throw on overflow addition', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => UInt32.MAXINT()); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.add(y); - }); - }).toThrow(); - }); - }); - - describe('sub', () => { - it('1-1=0', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.sub(y).assertEquals(new UInt32(Field(0))); - }); - }).not.toThrow(); - }); - - it('10000-5000=5000', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness( - UInt32, - () => new UInt32(Field(10000)) - ); - const y = Provable.witness(UInt32, () => new UInt32(Field(5000))); - x.sub(y).assertEquals(new UInt32(Field(5000))); - }); - }).not.toThrow(); - }); - - it('should throw on sub if results in negative number', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(0))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.sub(y); - }); - }).toThrow(); - }); - }); - - describe('mul', () => { - it('1x2=2', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1))); - const y = Provable.witness(UInt32, () => new UInt32(Field(2))); - x.mul(y).assertEquals(new UInt32(Field(2))); - }); - }).not.toThrow(); - }); - - it('1x0=0', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1))); - const y = Provable.witness(UInt32, () => new UInt32(Field(0))); - x.mul(y).assertEquals(new UInt32(Field(0))); - }); - }).not.toThrow(); - }); - - it('1000x1000=1000000', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1000))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1000))); - x.mul(y).assertEquals(new UInt32(Field(1000000))); - }); - }).not.toThrow(); - }); - - it('MAXINTx1=MAXINT', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => UInt32.MAXINT()); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.mul(y).assertEquals(UInt32.MAXINT()); - }); - }).not.toThrow(); - }); - - it('should throw on overflow multiplication', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => UInt32.MAXINT()); - const y = Provable.witness(UInt32, () => new UInt32(Field(2))); - x.mul(y); - }); - }).toThrow(); - }); - }); - - describe('div', () => { - it('2/1=2', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(2))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.div(y).assertEquals(new UInt32(Field(2))); - }); - }).not.toThrow(); - }); - - it('0/1=0', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(0))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.div(y).assertEquals(new UInt32(Field(0))); - }); - }).not.toThrow(); - }); - - it('2000/1000=2', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(2000))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1000))); - x.div(y).assertEquals(new UInt32(Field(2))); - }); - }).not.toThrow(); - }); - - it('MAXINT/1=MAXINT', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => UInt32.MAXINT()); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.div(y).assertEquals(UInt32.MAXINT()); - }); - }).not.toThrow(); - }); - - it('should throw on division by zero', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => UInt32.MAXINT()); - const y = Provable.witness(UInt32, () => new UInt32(Field(0))); - x.div(y); - }); - }).toThrow(); - }); - }); - - describe('mod', () => { - it('1%1=0', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.mod(y).assertEquals(new UInt32(Field(0))); - }); - }).not.toThrow(); - }); - - it('500%32=20', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(500))); - const y = Provable.witness(UInt32, () => new UInt32(Field(32))); - x.mod(y).assertEquals(new UInt32(Field(20))); - }); - }).not.toThrow(); - }); - - it('MAXINT%7=3', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => UInt32.MAXINT()); - const y = Provable.witness(UInt32, () => new UInt32(Field(7))); - x.mod(y).assertEquals(new UInt32(Field(3))); - }); - }).not.toThrow(); - }); - - it('should throw on mod by zero', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => UInt32.MAXINT()); - const y = Provable.witness(UInt32, () => new UInt32(Field(0))); - x.mod(y).assertEquals(new UInt32(Field(1))); - }); - }).toThrow(); - }); - }); - - describe('assertLt', () => { - it('1<2=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1))); - const y = Provable.witness(UInt32, () => new UInt32(Field(2))); - x.assertLessThan(y); - }); - }).not.toThrow(); - }); - - it('1<1=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.assertLessThan(y); - }); - }).toThrow(); - }); - - it('2<1=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(2))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.assertLessThan(y); - }); - }).toThrow(); - }); - - it('1000<100000=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1000))); - const y = Provable.witness( - UInt32, - () => new UInt32(Field(100000)) - ); - x.assertLessThan(y); - }); - }).not.toThrow(); - }); - - it('100000<1000=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness( - UInt32, - () => new UInt32(Field(100000)) - ); - const y = Provable.witness(UInt32, () => new UInt32(Field(1000))); - x.assertLessThan(y); - }); - }).toThrow(); - }); - - it('MAXINT { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => UInt32.MAXINT()); - const y = Provable.witness(UInt32, () => UInt32.MAXINT()); - x.assertLessThan(y); - }); - }).toThrow(); - }); - }); - - describe('assertLessThanOrEqual', () => { - it('1<=1=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.assertLessThanOrEqual(y); - }); - }).not.toThrow(); - }); - - it('2<=1=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(2))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.assertLessThanOrEqual(y); - }); - }).toThrow(); - }); - - it('1000<=100000=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1000))); - const y = Provable.witness( - UInt32, - () => new UInt32(Field(100000)) - ); - x.assertLessThanOrEqual(y); - }); - }).not.toThrow(); - }); - - it('100000<=1000=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness( - UInt32, - () => new UInt32(Field(100000)) - ); - const y = Provable.witness(UInt32, () => new UInt32(Field(1000))); - x.assertLessThanOrEqual(y); - }); - }).toThrow(); - }); - - it('MAXINT<=MAXINT=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => UInt32.MAXINT()); - const y = Provable.witness(UInt32, () => UInt32.MAXINT()); - x.assertLessThanOrEqual(y); - }); - }).not.toThrow(); - }); - }); - - describe('assertGreaterThan', () => { - it('2>1=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(2))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.assertGreaterThan(y); - }); - }).not.toThrow(); - }); - - it('1>1=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.assertGreaterThan(y); - }); - }).toThrow(); - }); - - it('1>2=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1))); - const y = Provable.witness(UInt32, () => new UInt32(Field(2))); - x.assertGreaterThan(y); - }); - }).toThrow(); - }); - - it('100000>1000=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness( - UInt32, - () => new UInt32(Field(100000)) - ); - const y = Provable.witness(UInt32, () => new UInt32(Field(1000))); - x.assertGreaterThan(y); - }); - }).not.toThrow(); - }); - - it('1000>100000=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1000))); - const y = Provable.witness( - UInt32, - () => new UInt32(Field(100000)) - ); - x.assertGreaterThan(y); - }); - }).toThrow(); - }); - - it('MAXINT>MAXINT=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => UInt32.MAXINT()); - const y = Provable.witness(UInt32, () => UInt32.MAXINT()); - x.assertGreaterThan(y); - }); - }).toThrow(); - }); - }); - - describe('assertGreaterThanOrEqual', () => { - it('1<=1=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1))); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.assertGreaterThanOrEqual(y); - }); - }).not.toThrow(); - }); - - it('1>=2=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1))); - const y = Provable.witness(UInt32, () => new UInt32(Field(2))); - x.assertGreaterThanOrEqual(y); - }); - }).toThrow(); - }); - - it('100000>=1000=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness( - UInt32, - () => new UInt32(Field(100000)) - ); - const y = Provable.witness(UInt32, () => new UInt32(Field(1000))); - x.assertGreaterThanOrEqual(y); - }); - }).not.toThrow(); - }); - - it('1000>=100000=false', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => new UInt32(Field(1000))); - const y = Provable.witness( - UInt32, - () => new UInt32(Field(100000)) - ); - x.assertGreaterThanOrEqual(y); - }); - }).toThrow(); - }); - - it('MAXINT>=MAXINT=true', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => UInt32.MAXINT()); - const y = Provable.witness(UInt32, () => UInt32.MAXINT()); - x.assertGreaterThanOrEqual(y); - }); - }).not.toThrow(); - }); - }); - - describe('from() ', () => { - describe('fromNumber()', () => { - it('should be the same as Field(1)', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => UInt32.from(1)); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.assertEquals(y); - }); - }).not.toThrow(); - }); - - it('should be the same as 2^53-1', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => - UInt32.from(NUMBERMAX) - ); - const y = Provable.witness( - UInt32, - () => new UInt32(Field(String(NUMBERMAX))) - ); - x.assertEquals(y); - }); - }).not.toThrow(); - }); - }); - describe('fromString()', () => { - it('should be the same as Field(1)', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => UInt32.from('1')); - const y = Provable.witness(UInt32, () => new UInt32(Field(1))); - x.assertEquals(y); - }); - }).not.toThrow(); - }); - - it('should be the same as 2^53-1', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt32, () => - UInt32.from(String(NUMBERMAX)) - ); - const y = Provable.witness( - UInt32, - () => new UInt32(Field(String(NUMBERMAX))) - ); - x.assertEquals(y); - }); - }).not.toThrow(); - }); - }); - }); - }); - - describe('Outside of circuit', () => { - describe('add', () => { - it('1+1=2', () => { - expect(new UInt32(Field(1)).add(1).toString()).toEqual('2'); - }); - - it('5000+5000=10000', () => { - expect(new UInt32(Field(5000)).add(5000).toString()).toEqual('10000'); - }); - - it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { - const value = Field((((1n << 32n) - 2n) / 2n).toString()); - expect( - new UInt32(value) - .add(new UInt32(value)) - .add(new UInt32(Field(1))) - .toString() - ).toEqual(UInt32.MAXINT().toString()); - }); - - it('should throw on overflow addition', () => { - expect(() => { - UInt32.MAXINT().add(1); - }).toThrow(); - }); - }); - - describe('sub', () => { - it('1-1=0', () => { - expect(new UInt32(Field(1)).sub(1).toString()).toEqual('0'); - }); - - it('10000-5000=5000', () => { - expect(new UInt32(Field(10000)).sub(5000).toString()).toEqual('5000'); - }); - - it('should throw on sub if results in negative number', () => { - expect(() => { - UInt32.from(0).sub(1); - }).toThrow(); - }); - }); - - describe('mul', () => { - it('1x2=2', () => { - expect(new UInt32(Field(1)).mul(2).toString()).toEqual('2'); - }); - - it('1x0=0', () => { - expect(new UInt32(Field(1)).mul(0).toString()).toEqual('0'); - }); - - it('1000x1000=1000000', () => { - expect(new UInt32(Field(1000)).mul(1000).toString()).toEqual( - '1000000' - ); - }); - - it('MAXINTx1=MAXINT', () => { - expect(UInt32.MAXINT().mul(1).toString()).toEqual( - UInt32.MAXINT().toString() - ); - }); - - it('should throw on overflow multiplication', () => { - expect(() => { - UInt32.MAXINT().mul(2); - }).toThrow(); - }); - }); - - describe('div', () => { - it('2/1=2', () => { - expect(new UInt32(Field(2)).div(1).toString()).toEqual('2'); - }); - - it('0/1=0', () => { - expect(new UInt32(Field(0)).div(1).toString()).toEqual('0'); - }); - - it('2000/1000=2', () => { - expect(new UInt32(Field(2000)).div(1000).toString()).toEqual('2'); - }); - - it('MAXINT/1=MAXINT', () => { - expect(UInt32.MAXINT().div(1).toString()).toEqual( - UInt32.MAXINT().toString() - ); - }); - - it('should throw on division by zero', () => { - expect(() => { - UInt32.MAXINT().div(0); - }).toThrow(); - }); - }); - - describe('mod', () => { - it('1%1=0', () => { - expect(new UInt32(Field(1)).mod(1).toString()).toEqual('0'); - }); - - it('500%32=20', () => { - expect(new UInt32(Field(500)).mod(32).toString()).toEqual('20'); - }); - - it('MAXINT%7=3', () => { - expect(UInt32.MAXINT().mod(7).toString()).toEqual('3'); - }); - - it('should throw on mod by zero', () => { - expect(() => { - UInt32.MAXINT().mod(0); - }).toThrow(); - }); - }); - - describe('lessThan', () => { - it('1<2=true', () => { - expect(new UInt32(Field(1)).lessThan(new UInt32(Field(2)))).toEqual( - Bool(true) - ); - }); - - it('1<1=false', () => { - expect(new UInt32(Field(1)).lessThan(new UInt32(Field(1)))).toEqual( - Bool(false) - ); - }); - - it('2<1=false', () => { - expect(new UInt32(Field(2)).lessThan(new UInt32(Field(1)))).toEqual( - Bool(false) - ); - }); - - it('1000<100000=true', () => { - expect( - new UInt32(Field(1000)).lessThan(new UInt32(Field(100000))) - ).toEqual(Bool(true)); - }); - - it('100000<1000=false', () => { - expect( - new UInt32(Field(100000)).lessThan(new UInt32(Field(1000))) - ).toEqual(Bool(false)); - }); - - it('MAXINT { - expect(UInt32.MAXINT().lessThan(UInt32.MAXINT())).toEqual( - Bool(false) - ); - }); - }); - - describe('lessThanOrEqual', () => { - it('1<=1=true', () => { - expect( - new UInt32(Field(1)).lessThanOrEqual(new UInt32(Field(1))) - ).toEqual(Bool(true)); - }); - - it('2<=1=false', () => { - expect( - new UInt32(Field(2)).lessThanOrEqual(new UInt32(Field(1))) - ).toEqual(Bool(false)); - }); - - it('1000<=100000=true', () => { - expect( - new UInt32(Field(1000)).lessThanOrEqual(new UInt32(Field(100000))) - ).toEqual(Bool(true)); - }); - - it('100000<=1000=false', () => { - expect( - new UInt32(Field(100000)).lessThanOrEqual(new UInt32(Field(1000))) - ).toEqual(Bool(false)); - }); - - it('MAXINT<=MAXINT=true', () => { - expect(UInt32.MAXINT().lessThanOrEqual(UInt32.MAXINT())).toEqual( - Bool(true) - ); - }); - }); - - describe('assertLessThanOrEqual', () => { - it('1<=1=true', () => { - expect(() => { - new UInt32(Field(1)).assertLessThanOrEqual(new UInt32(Field(1))); - }).not.toThrow(); - }); - - it('2<=1=false', () => { - expect(() => { - new UInt32(Field(2)).assertLessThanOrEqual(new UInt32(Field(1))); - }).toThrow(); - }); - - it('1000<=100000=true', () => { - expect(() => { - new UInt32(Field(1000)).assertLessThanOrEqual( - new UInt32(Field(100000)) - ); - }).not.toThrow(); - }); - - it('100000<=1000=false', () => { - expect(() => { - new UInt32(Field(100000)).assertLessThanOrEqual( - new UInt32(Field(1000)) - ); - }).toThrow(); - }); - - it('MAXINT<=MAXINT=true', () => { - expect(() => { - UInt32.MAXINT().assertLessThanOrEqual(UInt32.MAXINT()); - }).not.toThrow(); - }); - }); - - describe('greaterThan', () => { - it('2>1=true', () => { - expect( - new UInt32(Field(2)).greaterThan(new UInt32(Field(1))) - ).toEqual(Bool(true)); - }); - - it('1>1=false', () => { - expect( - new UInt32(Field(1)).greaterThan(new UInt32(Field(1))) - ).toEqual(Bool(false)); - }); - - it('1>2=false', () => { - expect( - new UInt32(Field(1)).greaterThan(new UInt32(Field(2))) - ).toEqual(Bool(false)); - }); - - it('100000>1000=true', () => { - expect( - new UInt32(Field(100000)).greaterThan(new UInt32(Field(1000))) - ).toEqual(Bool(true)); - }); - - it('1000>100000=false', () => { - expect( - new UInt32(Field(1000)).greaterThan(new UInt32(Field(100000))) - ).toEqual(Bool(false)); - }); - - it('MAXINT>MAXINT=false', () => { - expect(UInt32.MAXINT().greaterThan(UInt32.MAXINT())).toEqual( - Bool(false) - ); - }); - }); - - describe('assertGreaterThan', () => { - it('1>1=false', () => { - expect(() => { - new UInt32(Field(1)).assertGreaterThan(new UInt32(Field(1))); - }).toThrow(); - }); - - it('2>1=true', () => { - expect(() => { - new UInt32(Field(2)).assertGreaterThan(new UInt32(Field(1))); - }).not.toThrow(); - }); - - it('1000>100000=false', () => { - expect(() => { - new UInt32(Field(1000)).assertGreaterThan( - new UInt32(Field(100000)) - ); - }).toThrow(); - }); - - it('100000>1000=true', () => { - expect(() => { - new UInt32(Field(100000)).assertGreaterThan( - new UInt32(Field(1000)) - ); - }).not.toThrow(); - }); - - it('MAXINT>MAXINT=false', () => { - expect(() => { - UInt32.MAXINT().assertGreaterThan(UInt32.MAXINT()); - }).toThrow(); - }); - }); - - describe('greaterThanOrEqual', () => { - it('2>=1=true', () => { - expect( - new UInt32(Field(2)).greaterThanOrEqual(new UInt32(Field(1))) - ).toEqual(Bool(true)); - }); - - it('1>=1=true', () => { - expect( - new UInt32(Field(1)).greaterThanOrEqual(new UInt32(Field(1))) - ).toEqual(Bool(true)); - }); - - it('1>=2=false', () => { - expect( - new UInt32(Field(1)).greaterThanOrEqual(new UInt32(Field(2))) - ).toEqual(Bool(false)); - }); - - it('100000>=1000=true', () => { - expect( - new UInt32(Field(100000)).greaterThanOrEqual( - new UInt32(Field(1000)) - ) - ).toEqual(Bool(true)); - }); - - it('1000>=100000=false', () => { - expect( - new UInt32(Field(1000)).greaterThanOrEqual( - new UInt32(Field(100000)) - ) - ).toEqual(Bool(false)); - }); - - it('MAXINT>=MAXINT=true', () => { - expect(UInt32.MAXINT().greaterThanOrEqual(UInt32.MAXINT())).toEqual( - Bool(true) - ); - }); - }); - - describe('assertGreaterThanOrEqual', () => { - it('1>=1=true', () => { - expect(() => { - new UInt32(Field(1)).assertGreaterThanOrEqual(new UInt32(Field(1))); - }).not.toThrow(); - }); - - it('2>=1=true', () => { - expect(() => { - new UInt32(Field(2)).assertGreaterThanOrEqual(new UInt32(Field(1))); - }).not.toThrow(); - }); - - it('1000>=100000=false', () => { - expect(() => { - new UInt32(Field(1000)).assertGreaterThanOrEqual( - new UInt32(Field(100000)) - ); - }).toThrow(); - }); - - it('100000>=1000=true', () => { - expect(() => { - new UInt32(Field(100000)).assertGreaterThanOrEqual( - new UInt32(Field(1000)) - ); - }).not.toThrow(); - }); - - it('MAXINT>=MAXINT=true', () => { - expect(() => { - UInt32.MAXINT().assertGreaterThanOrEqual(UInt32.MAXINT()); - }).not.toThrow(); - }); - }); - - describe('toString()', () => { - it('should be the same as Field(0)', async () => { - const x = new UInt32(Field(0)); - const y = Field(0); - expect(x.toString()).toEqual(y.toString()); - }); - it('should be the same as 2^32-1', async () => { - const x = new UInt32(Field(String(NUMBERMAX))); - const y = Field(String(NUMBERMAX)); - expect(x.toString()).toEqual(y.toString()); - }); - }); - - describe('check()', () => { - it('should pass checking a MAXINT', () => { - expect(() => { - UInt32.check(UInt32.MAXINT()); - }).not.toThrow(); - }); - - it('should throw checking over MAXINT', () => { - const x = new UInt32(Field((1n << 32n).toString())); // This number is defined in UInt32.MAXINT() - expect(() => { - UInt32.check(x); - }).toThrow(); - }); - }); - - describe('from() ', () => { - describe('fromNumber()', () => { - it('should be the same as Field(1)', () => { - const x = UInt32.from(1); - expect(x.value).toEqual(new UInt32(Field(1)).value); - }); - - it('should be the same as 2^53-1', () => { - const x = UInt32.from(NUMBERMAX); - expect(x.value).toEqual(Field(String(NUMBERMAX))); - }); - }); - describe('fromString()', () => { - it('should be the same as Field(1)', () => { - const x = UInt32.from('1'); - expect(x.value).toEqual(new UInt32(Field(1)).value); - }); - - it('should be the same as 2^53-1', () => { - const x = UInt32.from(String(NUMBERMAX)); - expect(x.value).toEqual(Field(String(NUMBERMAX))); - }); - }); - }); - }); - }); -}); diff --git a/src/lib/int.ts b/src/lib/int.ts deleted file mode 100644 index 39744a84cf..0000000000 --- a/src/lib/int.ts +++ /dev/null @@ -1,961 +0,0 @@ -import { Field, Bool } from './core.js'; -import { AnyConstructor, CircuitValue, prop } from './circuit_value.js'; -import { Types } from '../bindings/mina-transaction/types.js'; -import { HashInput } from './hash.js'; -import { Provable } from './provable.js'; - -// external API -export { UInt32, UInt64, Int64, Sign }; - -/** - * A 64 bit unsigned integer with values ranging from 0 to 18,446,744,073,709,551,615. - */ -class UInt64 extends CircuitValue { - @prop value: Field; - static NUM_BITS = 64; - - /** - * Static method to create a {@link UInt64} with value `0`. - */ - static get zero() { - return new UInt64(Field(0)); - } - /** - * Static method to create a {@link UInt64} with value `1`. - */ - static get one() { - return new UInt64(Field(1)); - } - /** - * Turns the {@link UInt64} into a string. - * @returns - */ - toString() { - return this.value.toString(); - } - /** - * Turns the {@link UInt64} into a {@link BigInt}. - * @returns - */ - toBigInt() { - return this.value.toBigInt(); - } - - /** - * Turns the {@link UInt64} into a {@link UInt32}, asserting that it fits in 32 bits. - */ - toUInt32() { - let uint32 = new UInt32(this.value); - UInt32.check(uint32); - return uint32; - } - - /** - * Turns the {@link UInt64} into a {@link UInt32}, clamping to the 32 bits range if it's too large. - * ```ts - * UInt64.from(4294967296).toUInt32Clamped().toString(); // "4294967295" - * ``` - */ - toUInt32Clamped() { - let max = (1n << 32n) - 1n; - return Provable.if( - this.greaterThan(UInt64.from(max)), - UInt32.from(max), - new UInt32(this.value) - ); - } - - static check(x: UInt64) { - let actual = x.value.rangeCheckHelper(64); - actual.assertEquals(x.value); - } - static toInput(x: UInt64): HashInput { - return { packed: [[x.value, 64]] }; - } - /** - * Encodes this structure into a JSON-like object. - */ - static toJSON(x: UInt64) { - return x.value.toString(); - } - - /** - * Decodes a JSON-like object into this structure. - */ - static fromJSON(x: string): InstanceType { - return this.from(x) as any; - } - - private static checkConstant(x: Field) { - if (!x.isConstant()) return x; - let xBig = x.toBigInt(); - if (xBig < 0n || xBig >= 1n << BigInt(this.NUM_BITS)) { - throw Error( - `UInt64: Expected number between 0 and 2^64 - 1, got ${xBig}` - ); - } - return x; - } - - // this checks the range if the argument is a constant - /** - * Creates a new {@link UInt64}. - */ - static from(x: UInt64 | UInt32 | Field | number | string | bigint) { - if (x instanceof UInt64 || x instanceof UInt32) x = x.value; - return new this(this.checkConstant(Field(x))); - } - - /** - * Creates a {@link UInt64} with a value of 18,446,744,073,709,551,615. - */ - static MAXINT() { - return new UInt64(Field((1n << 64n) - 1n)); - } - - /** - * Integer division with remainder. - * - * `x.divMod(y)` returns the quotient and the remainder. - */ - divMod(y: UInt64 | number | string) { - let x = this.value; - let y_ = UInt64.from(y).value; - - if (this.value.isConstant() && y_.isConstant()) { - let xn = x.toBigInt(); - let yn = y_.toBigInt(); - let q = xn / yn; - let r = xn - q * yn; - return { - quotient: new UInt64(Field(q)), - rest: new UInt64(Field(r)), - }; - } - - y_ = y_.seal(); - - let q = Provable.witness( - Field, - () => new Field(x.toBigInt() / y_.toBigInt()) - ); - - q.rangeCheckHelper(UInt64.NUM_BITS).assertEquals(q); - - // TODO: Could be a bit more efficient - let r = x.sub(q.mul(y_)).seal(); - r.rangeCheckHelper(UInt64.NUM_BITS).assertEquals(r); - - let r_ = new UInt64(r); - let q_ = new UInt64(q); - - r_.assertLessThan(new UInt64(y_)); - - return { quotient: q_, rest: r_ }; - } - - /** - * Integer division. - * - * `x.div(y)` returns the floor of `x / y`, that is, the greatest - * `z` such that `z * y <= x`. - * - */ - div(y: UInt64 | number) { - return this.divMod(y).quotient; - } - - /** - * Integer remainder. - * - * `x.mod(y)` returns the value `z` such that `0 <= z < y` and - * `x - z` is divisble by `y`. - */ - mod(y: UInt64 | number) { - return this.divMod(y).rest; - } - - /** - * Multiplication with overflow checking. - */ - mul(y: UInt64 | number) { - let z = this.value.mul(UInt64.from(y).value); - z.rangeCheckHelper(UInt64.NUM_BITS).assertEquals(z); - return new UInt64(z); - } - - /** - * Addition with overflow checking. - */ - add(y: UInt64 | number) { - let z = this.value.add(UInt64.from(y).value); - z.rangeCheckHelper(UInt64.NUM_BITS).assertEquals(z); - return new UInt64(z); - } - - /** - * Subtraction with underflow checking. - */ - sub(y: UInt64 | number) { - let z = this.value.sub(UInt64.from(y).value); - z.rangeCheckHelper(UInt64.NUM_BITS).assertEquals(z); - return new UInt64(z); - } - - /** - * @deprecated Use {@link lessThanOrEqual} instead. - * - * Checks if a {@link UInt64} is less than or equal to another one. - */ - lte(y: UInt64) { - if (this.value.isConstant() && y.value.isConstant()) { - return Bool(this.value.toBigInt() <= y.value.toBigInt()); - } else { - let xMinusY = this.value.sub(y.value).seal(); - let yMinusX = xMinusY.neg(); - let xMinusYFits = xMinusY - .rangeCheckHelper(UInt64.NUM_BITS) - .equals(xMinusY); - let yMinusXFits = yMinusX - .rangeCheckHelper(UInt64.NUM_BITS) - .equals(yMinusX); - xMinusYFits.or(yMinusXFits).assertEquals(true); - // x <= y if y - x fits in 64 bits - return yMinusXFits; - } - } - - /** - * Checks if a {@link UInt64} is less than or equal to another one. - */ - lessThanOrEqual(y: UInt64) { - if (this.value.isConstant() && y.value.isConstant()) { - return Bool(this.value.toBigInt() <= y.value.toBigInt()); - } else { - let xMinusY = this.value.sub(y.value).seal(); - let yMinusX = xMinusY.neg(); - let xMinusYFits = xMinusY - .rangeCheckHelper(UInt64.NUM_BITS) - .equals(xMinusY); - let yMinusXFits = yMinusX - .rangeCheckHelper(UInt64.NUM_BITS) - .equals(yMinusX); - xMinusYFits.or(yMinusXFits).assertEquals(true); - // x <= y if y - x fits in 64 bits - return yMinusXFits; - } - } - - /** - * @deprecated Use {@link assertLessThanOrEqual} instead. - * - * Asserts that a {@link UInt64} is less than or equal to another one. - */ - assertLte(y: UInt64, message?: string) { - this.assertLessThanOrEqual(y, message); - } - - /** - * Asserts that a {@link UInt64} is less than or equal to another one. - */ - assertLessThanOrEqual(y: UInt64, message?: string) { - if (this.value.isConstant() && y.value.isConstant()) { - let x0 = this.value.toBigInt(); - let y0 = y.value.toBigInt(); - if (x0 > y0) { - if (message !== undefined) throw Error(message); - throw Error(`UInt64.assertLessThanOrEqual: expected ${x0} <= ${y0}`); - } - return; - } - let yMinusX = y.value.sub(this.value).seal(); - yMinusX.rangeCheckHelper(UInt64.NUM_BITS).assertEquals(yMinusX, message); - } - - /** - * @deprecated Use {@link lessThan} instead. - * - * Checks if a {@link UInt64} is less than another one. - */ - lt(y: UInt64) { - return this.lessThanOrEqual(y).and(this.value.equals(y.value).not()); - } - - /** - * - * Checks if a {@link UInt64} is less than another one. - */ - lessThan(y: UInt64) { - return this.lessThanOrEqual(y).and(this.value.equals(y.value).not()); - } - - /** - * - * @deprecated Use {@link assertLessThan} instead. - * - * Asserts that a {@link UInt64} is less than another one. - */ - assertLt(y: UInt64, message?: string) { - this.lessThan(y).assertEquals(true, message); - } - - /** - * Asserts that a {@link UInt64} is less than another one. - */ - assertLessThan(y: UInt64, message?: string) { - this.lessThan(y).assertEquals(true, message); - } - - /** - * @deprecated Use {@link greaterThan} instead. - * - * Checks if a {@link UInt64} is greater than another one. - */ - gt(y: UInt64) { - return y.lessThan(this); - } - - /** - * Checks if a {@link UInt64} is greater than another one. - */ - greaterThan(y: UInt64) { - return y.lessThan(this); - } - - /** - * @deprecated Use {@link assertGreaterThan} instead. - * - * Asserts that a {@link UInt64} is greater than another one. - */ - assertGt(y: UInt64, message?: string) { - y.assertLessThan(this, message); - } - - /** - * Asserts that a {@link UInt64} is greater than another one. - */ - assertGreaterThan(y: UInt64, message?: string) { - y.assertLessThan(this, message); - } - - /** - * @deprecated Use {@link greaterThanOrEqual} instead. - * - * Checks if a {@link UInt64} is greater than or equal to another one. - */ - gte(y: UInt64) { - return this.lessThan(y).not(); - } - - /** - * Checks if a {@link UInt64} is greater than or equal to another one. - */ - greaterThanOrEqual(y: UInt64) { - return this.lessThan(y).not(); - } - - /** - * @deprecated Use {@link assertGreaterThanOrEqual} instead. - * - * Asserts that a {@link UInt64} is greater than or equal to another one. - */ - assertGte(y: UInt64, message?: string) { - y.assertLessThanOrEqual(this, message); - } - - /** - * Asserts that a {@link UInt64} is greater than or equal to another one. - */ - assertGreaterThanOrEqual(y: UInt64, message?: string) { - y.assertLessThanOrEqual(this, message); - } -} -/** - * A 32 bit unsigned integer with values ranging from 0 to 4,294,967,295. - */ -class UInt32 extends CircuitValue { - @prop value: Field; - static NUM_BITS = 32; - - /** - * Static method to create a {@link UInt32} with value `0`. - */ - static get zero(): UInt32 { - return new UInt32(Field(0)); - } - - /** - * Static method to create a {@link UInt32} with value `0`. - */ - static get one(): UInt32 { - return new UInt32(Field(1)); - } - /** - * Turns the {@link UInt32} into a string. - */ - toString(): string { - return this.value.toString(); - } - /** - * Turns the {@link UInt32} into a {@link BigInt}. - */ - toBigint() { - return this.value.toBigInt(); - } - /** - * Turns the {@link UInt32} into a {@link UInt64}. - */ - toUInt64(): UInt64 { - // this is safe, because the UInt32 range is included in the UInt64 range - return new UInt64(this.value); - } - - static check(x: UInt32) { - let actual = x.value.rangeCheckHelper(32); - actual.assertEquals(x.value); - } - static toInput(x: UInt32): HashInput { - return { packed: [[x.value, 32]] }; - } - /** - * Encodes this structure into a JSON-like object. - */ - static toJSON(x: UInt32) { - return x.value.toString(); - } - - /** - * Decodes a JSON-like object into this structure. - */ - static fromJSON(x: string): InstanceType { - return this.from(x) as any; - } - - private static checkConstant(x: Field) { - if (!x.isConstant()) return x; - let xBig = x.toBigInt(); - if (xBig < 0n || xBig >= 1n << BigInt(this.NUM_BITS)) { - throw Error( - `UInt32: Expected number between 0 and 2^32 - 1, got ${xBig}` - ); - } - return x; - } - - // this checks the range if the argument is a constant - /** - * Creates a new {@link UInt32}. - */ - static from(x: UInt32 | Field | number | string | bigint) { - if (x instanceof UInt32) x = x.value; - return new this(this.checkConstant(Field(x))); - } - /** - * Creates a {@link UInt32} with a value of 4,294,967,295. - */ - static MAXINT() { - return new UInt32(Field((1n << 32n) - 1n)); - } - /** - * Integer division with remainder. - * - * `x.divMod(y)` returns the quotient and the remainder. - */ - divMod(y: UInt32 | number | string) { - let x = this.value; - let y_ = UInt32.from(y).value; - - if (x.isConstant() && y_.isConstant()) { - let xn = x.toBigInt(); - let yn = y_.toBigInt(); - let q = xn / yn; - let r = xn - q * yn; - return { - quotient: new UInt32(new Field(q.toString())), - rest: new UInt32(new Field(r.toString())), - }; - } - - y_ = y_.seal(); - - let q = Provable.witness( - Field, - () => new Field(x.toBigInt() / y_.toBigInt()) - ); - - q.rangeCheckHelper(UInt32.NUM_BITS).assertEquals(q); - - // TODO: Could be a bit more efficient - let r = x.sub(q.mul(y_)).seal(); - r.rangeCheckHelper(UInt32.NUM_BITS).assertEquals(r); - - let r_ = new UInt32(r); - let q_ = new UInt32(q); - - r_.assertLessThan(new UInt32(y_)); - - return { quotient: q_, rest: r_ }; - } - /** - * Integer division. - * - * `x.div(y)` returns the floor of `x / y`, that is, the greatest - * `z` such that `x * y <= x`. - * - */ - div(y: UInt32 | number) { - return this.divMod(y).quotient; - } - /** - * Integer remainder. - * - * `x.mod(y)` returns the value `z` such that `0 <= z < y` and - * `x - z` is divisble by `y`. - */ - mod(y: UInt32 | number) { - return this.divMod(y).rest; - } - /** - * Multiplication with overflow checking. - */ - mul(y: UInt32 | number) { - let z = this.value.mul(UInt32.from(y).value); - z.rangeCheckHelper(UInt32.NUM_BITS).assertEquals(z); - return new UInt32(z); - } - /** - * Addition with overflow checking. - */ - add(y: UInt32 | number) { - let z = this.value.add(UInt32.from(y).value); - z.rangeCheckHelper(UInt32.NUM_BITS).assertEquals(z); - return new UInt32(z); - } - /** - * Subtraction with underflow checking. - */ - sub(y: UInt32 | number) { - let z = this.value.sub(UInt32.from(y).value); - z.rangeCheckHelper(UInt32.NUM_BITS).assertEquals(z); - return new UInt32(z); - } - /** - * @deprecated Use {@link lessThanOrEqual} instead. - * - * Checks if a {@link UInt32} is less than or equal to another one. - */ - lte(y: UInt32) { - if (this.value.isConstant() && y.value.isConstant()) { - return Bool(this.value.toBigInt() <= y.value.toBigInt()); - } else { - let xMinusY = this.value.sub(y.value).seal(); - let yMinusX = xMinusY.neg(); - let xMinusYFits = xMinusY - .rangeCheckHelper(UInt32.NUM_BITS) - .equals(xMinusY); - let yMinusXFits = yMinusX - .rangeCheckHelper(UInt32.NUM_BITS) - .equals(yMinusX); - xMinusYFits.or(yMinusXFits).assertEquals(true); - // x <= y if y - x fits in 64 bits - return yMinusXFits; - } - } - - /** - * Checks if a {@link UInt32} is less than or equal to another one. - */ - lessThanOrEqual(y: UInt32) { - if (this.value.isConstant() && y.value.isConstant()) { - return Bool(this.value.toBigInt() <= y.value.toBigInt()); - } else { - let xMinusY = this.value.sub(y.value).seal(); - let yMinusX = xMinusY.neg(); - let xMinusYFits = xMinusY - .rangeCheckHelper(UInt32.NUM_BITS) - .equals(xMinusY); - let yMinusXFits = yMinusX - .rangeCheckHelper(UInt32.NUM_BITS) - .equals(yMinusX); - xMinusYFits.or(yMinusXFits).assertEquals(true); - // x <= y if y - x fits in 64 bits - return yMinusXFits; - } - } - - /** - * @deprecated Use {@link assertLessThanOrEqual} instead. - * - * Asserts that a {@link UInt32} is less than or equal to another one. - */ - assertLte(y: UInt32, message?: string) { - this.assertLessThanOrEqual(y, message); - } - - /** - * Asserts that a {@link UInt32} is less than or equal to another one. - */ - assertLessThanOrEqual(y: UInt32, message?: string) { - if (this.value.isConstant() && y.value.isConstant()) { - let x0 = this.value.toBigInt(); - let y0 = y.value.toBigInt(); - if (x0 > y0) { - if (message !== undefined) throw Error(message); - throw Error(`UInt32.assertLessThanOrEqual: expected ${x0} <= ${y0}`); - } - return; - } - let yMinusX = y.value.sub(this.value).seal(); - yMinusX.rangeCheckHelper(UInt32.NUM_BITS).assertEquals(yMinusX, message); - } - - /** - * @deprecated Use {@link lessThan} instead. - * - * Checks if a {@link UInt32} is less than another one. - */ - lt(y: UInt32) { - return this.lessThanOrEqual(y).and(this.value.equals(y.value).not()); - } - - /** - * Checks if a {@link UInt32} is less than another one. - */ - lessThan(y: UInt32) { - return this.lessThanOrEqual(y).and(this.value.equals(y.value).not()); - } - - /** - * @deprecated Use {@link assertLessThan} instead. - * - * Asserts that a {@link UInt32} is less than another one. - */ - assertLt(y: UInt32, message?: string) { - this.lessThan(y).assertEquals(true, message); - } - - /** - * Asserts that a {@link UInt32} is less than another one. - */ - assertLessThan(y: UInt32, message?: string) { - this.lessThan(y).assertEquals(true, message); - } - - /** - * @deprecated Use {@link greaterThan} instead. - * - * Checks if a {@link UInt32} is greater than another one. - */ - gt(y: UInt32) { - return y.lessThan(this); - } - - /** - * Checks if a {@link UInt32} is greater than another one. - */ - greaterThan(y: UInt32) { - return y.lessThan(this); - } - - /** - * @deprecated Use {@link assertGreaterThan} instead. - * - * Asserts that a {@link UInt32} is greater than another one. - */ - assertGt(y: UInt32, message?: string) { - y.assertLessThan(this, message); - } - - /** - * Asserts that a {@link UInt32} is greater than another one. - */ - assertGreaterThan(y: UInt32, message?: string) { - y.assertLessThan(this, message); - } - - /** - * @deprecated Use {@link greaterThanOrEqual} instead. - * - * Checks if a {@link UInt32} is greater than or equal to another one. - */ - gte(y: UInt32) { - return this.lessThan(y).not(); - } - - /** - * Checks if a {@link UInt32} is greater than or equal to another one. - */ - greaterThanOrEqual(y: UInt32) { - return this.lessThan(y).not(); - } - - /** - * @deprecated Use {@link assertGreaterThanOrEqual} instead. - - * - * Asserts that a {@link UInt32} is greater than or equal to another one. - */ - assertGte(y: UInt32, message?: string) { - y.assertLessThanOrEqual(this, message); - } - - /** - * Asserts that a {@link UInt32} is greater than or equal to another one. - */ - assertGreaterThanOrEqual(y: UInt32, message?: string) { - y.assertLessThanOrEqual(this, message); - } -} - -class Sign extends CircuitValue { - @prop value: Field; // +/- 1 - - static get one() { - return new Sign(Field(1)); - } - static get minusOne() { - return new Sign(Field(-1)); - } - static check(x: Sign) { - // x^2 === 1 <=> x === 1 or x === -1 - x.value.square().assertEquals(Field(1)); - } - static emptyValue(): Sign { - return Sign.one; - } - static toInput(x: Sign): HashInput { - return { packed: [[x.isPositive().toField(), 1]] }; - } - static toJSON(x: Sign) { - if (x.toString() === '1') return 'Positive'; - if (x.neg().toString() === '1') return 'Negative'; - throw Error(`Invalid Sign: ${x}`); - } - static fromJSON( - x: 'Positive' | 'Negative' - ): InstanceType { - return (x === 'Positive' ? new Sign(Field(1)) : new Sign(Field(-1))) as any; - } - neg() { - return new Sign(this.value.neg()); - } - mul(y: Sign) { - return new Sign(this.value.mul(y.value)); - } - isPositive() { - return this.value.equals(Field(1)); - } - toString() { - return this.value.toString(); - } -} - -type BalanceChange = Types.AccountUpdate['body']['balanceChange']; - -/** - * A 64 bit signed integer with values ranging from -18,446,744,073,709,551,615 to 18,446,744,073,709,551,615. - */ -class Int64 extends CircuitValue implements BalanceChange { - // * in the range [-2^64+1, 2^64-1], unlike a normal int64 - // * under- and overflowing is disallowed, similar to UInt64, unlike a normal int64+ - - @prop magnitude: UInt64; // absolute value - @prop sgn: Sign; // +/- 1 - - // Some thoughts regarding the representation as field elements: - // toFields returns the in-circuit representation, so the main objective is to minimize the number of constraints - // that result from this representation. Therefore, I think the only candidate for an efficient 1-field representation - // is the one where the Int64 is the field: toFields = Int64 => [Int64.magnitude.mul(Int64.sign)]. Anything else involving - // bit packing would just lead to very inefficient circuit operations. - // - // So, is magnitude * sign ("1-field") a more efficient representation than (magnitude, sign) ("2-field")? - // Several common operations like add, mul, etc, operate on 1-field so in 2-field they result in one additional multiplication - // constraint per operand. However, the check operation (constraining to 64 bits + a sign) which is called at the introduction - // of every witness, and also at the end of add, mul, etc, operates on 2-field. So here, the 1-field representation needs - // to add an additional magnitude * sign = Int64 multiplication constraint, which will typically cancel out most of the gains - // achieved by 1-field elsewhere. - // There are some notable operations for which 2-field is definitely better: - // - // * div and mod (which do integer division with rounding on the magnitude) - // * converting the Int64 to a Currency.Amount.Signed (for the zkapp balance), which has the exact same (magnitude, sign) representation we use here. - // - // The second point is one of the main things an Int64 is used for, and was the original motivation to use 2 fields. - // Overall, I think the existing implementation is the optimal one. - - constructor(magnitude: UInt64, sgn = Sign.one) { - super(magnitude, sgn); - } - - /** - * Creates a new {@link Int64} from a {@link Field}. - * - * Does check if the {@link Field} is within range. - */ - private static fromFieldUnchecked(x: Field) { - let TWO64 = 1n << 64n; - let xBigInt = x.toBigInt(); - let isValidPositive = xBigInt < TWO64; // covers {0,...,2^64 - 1} - let isValidNegative = Field.ORDER - xBigInt < TWO64; // {-2^64 + 1,...,-1} - if (!isValidPositive && !isValidNegative) - throw Error(`Int64: Expected a value between (-2^64, 2^64), got ${x}`); - let magnitude = Field(isValidPositive ? x.toString() : x.neg().toString()); - let sign = isValidPositive ? Sign.one : Sign.minusOne; - return new Int64(new UInt64(magnitude), sign); - } - - // this doesn't check ranges because we assume they're already checked on UInts - /** - * Creates a new {@link Int64} from a {@link Field}. - * - * **Does not** check if the {@link Field} is within range. - */ - static fromUnsigned(x: UInt64 | UInt32) { - return new Int64(x instanceof UInt32 ? x.toUInt64() : x); - } - - // this checks the range if the argument is a constant - /** - * Creates a new {@link Int64}. - * - * Check the range if the argument is a constant. - */ - static from(x: Int64 | UInt32 | UInt64 | Field | number | string | bigint) { - if (x instanceof Int64) return x; - if (x instanceof UInt64 || x instanceof UInt32) { - return Int64.fromUnsigned(x); - } - return Int64.fromFieldUnchecked(Field(x)); - } - /** - * Turns the {@link Int64} into a string. - */ - toString() { - let abs = this.magnitude.toString(); - let sgn = this.isPositive().toBoolean() || abs === '0' ? '' : '-'; - return sgn + abs; - } - isConstant() { - return this.magnitude.value.isConstant() && this.sgn.isConstant(); - } - - // --- circuit-compatible operations below --- - // the assumption here is that all Int64 values that appear in a circuit are already checked as valid - // this is because Provable.witness calls .check, which calls .check on each prop, i.e. UInt64 and Sign - // so we only have to do additional checks if an operation on valid inputs can have an invalid outcome (example: overflow) - /** - * Static method to create a {@link Int64} with value `0`. - */ - static get zero() { - return new Int64(UInt64.zero); - } - /** - * Static method to create a {@link Int64} with value `1`. - */ - static get one() { - return new Int64(UInt64.one); - } - /** - * Static method to create a {@link Int64} with value `-1`. - */ - static get minusOne() { - return new Int64(UInt64.one).neg(); - } - - /** - * Returns the {@link Field} value. - */ - toField() { - return this.magnitude.value.mul(this.sgn.value); - } - /** - * Static method to create a {@link Int64} from a {@link Field}. - */ - static fromField(x: Field): Int64 { - // constant case - just return unchecked value - if (x.isConstant()) return Int64.fromFieldUnchecked(x); - // variable case - create a new checked witness and prove consistency with original field - let xInt = Provable.witness(Int64, () => Int64.fromFieldUnchecked(x)); - xInt.toField().assertEquals(x); // sign(x) * |x| === x - return xInt; - } - - /** - * Negates the value. - * - * `Int64.from(5).neg()` will turn into `Int64.from(-5)` - */ - neg() { - // doesn't need further check if `this` is valid - return new Int64(this.magnitude, this.sgn.neg()); - } - /** - * Addition with overflow checking. - */ - add(y: Int64 | number | string | bigint | UInt64 | UInt32) { - let y_ = Int64.from(y); - return Int64.fromField(this.toField().add(y_.toField())); - } - /** - * Subtraction with underflow checking. - */ - sub(y: Int64 | number | string | bigint | UInt64 | UInt32) { - let y_ = Int64.from(y); - return Int64.fromField(this.toField().sub(y_.toField())); - } - /** - * Multiplication with overflow checking. - */ - mul(y: Int64 | number | string | bigint | UInt64 | UInt32) { - let y_ = Int64.from(y); - return Int64.fromField(this.toField().mul(y_.toField())); - } - /** - * Integer division. - * - * `x.div(y)` returns the floor of `x / y`, that is, the greatest - * `z` such that `z * y <= x`. - * - */ - div(y: Int64 | number | string | bigint | UInt64 | UInt32) { - let y_ = Int64.from(y); - let { quotient } = this.magnitude.divMod(y_.magnitude); - let sign = this.sgn.mul(y_.sgn); - return new Int64(quotient, sign); - } - /** - * Integer remainder. - * - * `x.mod(y)` returns the value `z` such that `0 <= z < y` and - * `x - z` is divisble by `y`. - */ - mod(y: UInt64 | number | string | bigint | UInt32) { - let y_ = UInt64.from(y); - let rest = this.magnitude.divMod(y_).rest.value; - rest = Provable.if(this.isPositive(), rest, y_.value.sub(rest)); - return new Int64(new UInt64(rest)); - } - - /** - * Checks if two values are equal. - */ - equals(y: Int64 | number | string | bigint | UInt64 | UInt32) { - let y_ = Int64.from(y); - return this.toField().equals(y_.toField()); - } - /** - * Asserts that two values are equal. - */ - assertEquals( - y: Int64 | number | string | bigint | UInt64 | UInt32, - message?: string - ) { - let y_ = Int64.from(y); - this.toField().assertEquals(y_.toField(), message); - } - /** - * Checks if the value is postive. - */ - isPositive() { - return this.sgn.isPositive(); - } -} diff --git a/src/lib/mina.ts b/src/lib/mina.ts deleted file mode 100644 index e115ef7f12..0000000000 --- a/src/lib/mina.ts +++ /dev/null @@ -1,1564 +0,0 @@ -import { Ledger } from '../snarky.js'; -import { Field } from './core.js'; -import { UInt32, UInt64 } from './int.js'; -import { PrivateKey, PublicKey } from './signature.js'; -import { - addMissingProofs, - addMissingSignatures, - FeePayerUnsigned, - ZkappCommand, - AccountUpdate, - ZkappPublicInput, - TokenId, - CallForest, - Authorization, - Actions, - Events, - dummySignature, -} from './account_update.js'; -import * as Fetch from './fetch.js'; -import { assertPreconditionInvariants, NetworkValue } from './precondition.js'; -import { cloneCircuitValue, toConstant } from './circuit_value.js'; -import { Empty, Proof, verify } from './proof_system.js'; -import { Context } from './global-context.js'; -import { SmartContract } from './zkapp.js'; -import { invalidTransactionError } from './mina/errors.js'; -import { Types, TypesBigint } from '../bindings/mina-transaction/types.js'; -import { Account } from './mina/account.js'; -import { TransactionCost, TransactionLimits } from './mina/constants.js'; -import { Provable } from './provable.js'; -import { prettifyStacktrace } from './errors.js'; -import { Ml } from './ml/conversion.js'; -import { - transactionCommitments, - verifyAccountUpdateSignature, -} from '../mina-signer/src/sign-zkapp-command.js'; - -export { - createTransaction, - BerkeleyQANet, - Network, - LocalBlockchain, - currentTransaction, - CurrentTransaction, - Transaction, - TransactionId, - activeInstance, - setActiveInstance, - transaction, - sender, - currentSlot, - getAccount, - hasAccount, - getBalance, - getNetworkState, - accountCreationFee, - sendTransaction, - fetchEvents, - fetchActions, - getActions, - FeePayerSpec, - ActionStates, - faucet, - waitForFunding, - getProofsEnabled, - // for internal testing only - filterGroups, -}; -interface TransactionId { - isSuccess: boolean; - wait(options?: { maxAttempts?: number; interval?: number }): Promise; - hash(): string | undefined; -} - -type Transaction = { - /** - * Transaction structure used to describe a state transition on the Mina blockchain. - */ - transaction: ZkappCommand; - /** - * Returns a JSON representation of the {@link Transaction}. - */ - toJSON(): string; - /** - * Returns a pretty-printed JSON representation of the {@link Transaction}. - */ - toPretty(): any; - /** - * Returns the GraphQL query for the Mina daemon. - */ - toGraphqlQuery(): string; - /** - * Signs all {@link AccountUpdate}s included in the {@link Transaction} that require a signature. - * - * {@link AccountUpdate}s that require a signature can be specified with `{AccountUpdate|SmartContract}.requireSignature()`. - * - * @param additionalKeys The list of keys that should be used to sign the {@link Transaction} - */ - sign(additionalKeys?: PrivateKey[]): Transaction; - /** - * Generates proofs for the {@link Transaction}. - * - * This can take some time. - */ - prove(): Promise<(Proof | undefined)[]>; - /** - * Sends the {@link Transaction} to the network. - */ - send(): Promise; -}; - -const Transaction = { - fromJSON(json: Types.Json.ZkappCommand): Transaction { - let transaction = ZkappCommand.fromJSON(json); - return newTransaction(transaction, activeInstance.proofsEnabled); - }, -}; - -type FetchMode = 'fetch' | 'cached' | 'test'; -type CurrentTransaction = { - sender?: PublicKey; - accountUpdates: AccountUpdate[]; - fetchMode: FetchMode; - isFinalRunOutsideCircuit: boolean; - numberOfRuns: 0 | 1 | undefined; -}; - -let currentTransaction = Context.create(); - -/** - * Allows you to specify information about the fee payer account and the transaction. - */ -type FeePayerSpec = - | PublicKey - | { - sender: PublicKey; - fee?: number | string | UInt64; - memo?: string; - nonce?: number; - } - | undefined; - -type DeprecatedFeePayerSpec = - | PublicKey - | PrivateKey - | (( - | { - feePayerKey: PrivateKey; - sender?: PublicKey; - } - | { - feePayerKey?: PrivateKey; - sender: PublicKey; - } - ) & { - fee?: number | string | UInt64; - memo?: string; - nonce?: number; - }) - | undefined; - -type ActionStates = { - fromActionState?: Field; - endActionState?: Field; -}; - -function reportGetAccountError(publicKey: string, tokenId: string) { - if (tokenId === TokenId.toBase58(TokenId.default)) { - return `getAccount: Could not find account for public key ${publicKey}`; - } else { - return `getAccount: Could not find account for public key ${publicKey} with the tokenId ${tokenId}`; - } -} - -function createTransaction( - feePayer: DeprecatedFeePayerSpec, - f: () => unknown, - numberOfRuns: 0 | 1 | undefined, - { - fetchMode = 'cached' as FetchMode, - isFinalRunOutsideCircuit = true, - proofsEnabled = true, - } = {} -): Transaction { - if (currentTransaction.has()) { - throw new Error('Cannot start new transaction within another transaction'); - } - let feePayerSpec: { - sender?: PublicKey; - feePayerKey?: PrivateKey; - fee?: number | string | UInt64; - memo?: string; - nonce?: number; - }; - if (feePayer === undefined) { - feePayerSpec = {}; - } else if (feePayer instanceof PrivateKey) { - feePayerSpec = { feePayerKey: feePayer, sender: feePayer.toPublicKey() }; - } else if (feePayer instanceof PublicKey) { - feePayerSpec = { sender: feePayer }; - } else { - feePayerSpec = feePayer; - if (feePayerSpec.sender === undefined) - feePayerSpec.sender = feePayerSpec.feePayerKey?.toPublicKey(); - } - let { feePayerKey, sender, fee, memo = '', nonce } = feePayerSpec; - - let transactionId = currentTransaction.enter({ - sender, - accountUpdates: [], - fetchMode, - isFinalRunOutsideCircuit, - numberOfRuns, - }); - - // run circuit - // we have this while(true) loop because one of the smart contracts we're calling inside `f` might be calling - // SmartContract.analyzeMethods, which would be running its methods again inside `Provable.constraintSystem`, which - // would throw an error when nested inside `Provable.runAndCheck`. So if that happens, we have to run `analyzeMethods` first - // and retry `Provable.runAndCheck(f)`. Since at this point in the function, we don't know which smart contracts are involved, - // we created that hack with a `bootstrap()` function that analyzeMethods sticks on the error, to call itself again. - try { - let err: any; - while (true) { - if (err !== undefined) err.bootstrap(); - try { - if (fetchMode === 'test') { - Provable.runUnchecked(() => { - f(); - Provable.asProver(() => { - let tx = currentTransaction.get(); - tx.accountUpdates = CallForest.map(tx.accountUpdates, (a) => - toConstant(AccountUpdate, a) - ); - }); - }); - } else { - f(); - } - break; - } catch (err_) { - if ((err_ as any)?.bootstrap) err = err_; - else throw err_; - } - } - } catch (err) { - currentTransaction.leave(transactionId); - throw err; - } - let accountUpdates = currentTransaction.get().accountUpdates; - // TODO: I'll be back - // CallForest.addCallers(accountUpdates); - accountUpdates = CallForest.toFlatList(accountUpdates); - - try { - // check that on-chain values weren't used without setting a precondition - for (let accountUpdate of accountUpdates) { - assertPreconditionInvariants(accountUpdate); - } - } catch (err) { - currentTransaction.leave(transactionId); - throw err; - } - - let feePayerAccountUpdate: FeePayerUnsigned; - if (sender !== undefined) { - // if senderKey is provided, fetch account to get nonce and mark to be signed - let nonce_; - let senderAccount = getAccount(sender, TokenId.default); - - if (nonce === undefined) { - nonce_ = senderAccount.nonce; - } else { - nonce_ = UInt32.from(nonce); - senderAccount.nonce = nonce_; - Fetch.addCachedAccount(senderAccount); - } - feePayerAccountUpdate = AccountUpdate.defaultFeePayer(sender, nonce_); - if (feePayerKey !== undefined) - feePayerAccountUpdate.lazyAuthorization!.privateKey = feePayerKey; - if (fee !== undefined) { - feePayerAccountUpdate.body.fee = - fee instanceof UInt64 ? fee : UInt64.from(String(fee)); - } - } else { - // otherwise use a dummy fee payer that has to be filled in later - feePayerAccountUpdate = AccountUpdate.dummyFeePayer(); - } - - let transaction: ZkappCommand = { - accountUpdates, - feePayer: feePayerAccountUpdate, - memo, - }; - - currentTransaction.leave(transactionId); - return newTransaction(transaction, proofsEnabled); -} - -function newTransaction(transaction: ZkappCommand, proofsEnabled?: boolean) { - let self: Transaction = { - transaction, - sign(additionalKeys?: PrivateKey[]) { - self.transaction = addMissingSignatures(self.transaction, additionalKeys); - return self; - }, - async prove() { - let { zkappCommand, proofs } = await addMissingProofs(self.transaction, { - proofsEnabled, - }); - self.transaction = zkappCommand; - return proofs; - }, - toJSON() { - let json = ZkappCommand.toJSON(self.transaction); - return JSON.stringify(json); - }, - toPretty() { - return ZkappCommand.toPretty(self.transaction); - }, - toGraphqlQuery() { - return Fetch.sendZkappQuery(self.toJSON()); - }, - async send() { - try { - return await sendTransaction(self); - } catch (error) { - throw prettifyStacktrace(error); - } - }, - }; - return self; -} - -interface Mina { - transaction( - sender: DeprecatedFeePayerSpec, - f: () => void - ): Promise; - currentSlot(): UInt32; - hasAccount(publicKey: PublicKey, tokenId?: Field): boolean; - getAccount(publicKey: PublicKey, tokenId?: Field): Account; - getNetworkState(): NetworkValue; - getNetworkConstants(): { - genesisTimestamp: UInt64; - /** - * Duration of 1 slot in millisecondw - */ - slotTime: UInt64; - accountCreationFee: UInt64; - }; - accountCreationFee(): UInt64; - sendTransaction(transaction: Transaction): Promise; - fetchEvents: ( - publicKey: PublicKey, - tokenId?: Field, - filterOptions?: Fetch.EventActionFilterOptions - ) => ReturnType; - fetchActions: ( - publicKey: PublicKey, - actionStates?: ActionStates, - tokenId?: Field - ) => ReturnType; - getActions: ( - publicKey: PublicKey, - actionStates?: ActionStates, - tokenId?: Field - ) => { hash: string; actions: string[][] }[]; - proofsEnabled: boolean; -} - -const defaultAccountCreationFee = 1_000_000_000; - -/** - * A mock Mina blockchain running locally and useful for testing. - */ -function LocalBlockchain({ - accountCreationFee = defaultAccountCreationFee as string | number, - proofsEnabled = true, - enforceTransactionLimits = true, -} = {}) { - const slotTime = 3 * 60 * 1000; - const startTime = Date.now(); - const genesisTimestamp = UInt64.from(startTime); - - const ledger = Ledger.create(); - - let networkState = defaultNetworkState(); - - function addAccount(publicKey: PublicKey, balance: string) { - ledger.addAccount(Ml.fromPublicKey(publicKey), balance); - } - - let testAccounts: { - publicKey: PublicKey; - privateKey: PrivateKey; - }[] = []; - - for (let i = 0; i < 10; ++i) { - let MINA = 10n ** 9n; - const largeValue = 1000n * MINA; - const k = PrivateKey.random(); - const pk = k.toPublicKey(); - addAccount(pk, largeValue.toString()); - testAccounts.push({ privateKey: k, publicKey: pk }); - } - - const events: Record = {}; - const actions: Record< - string, - Record - > = {}; - - return { - proofsEnabled, - accountCreationFee: () => UInt64.from(accountCreationFee), - getNetworkConstants() { - return { - genesisTimestamp, - accountCreationFee: UInt64.from(accountCreationFee), - slotTime: UInt64.from(slotTime), - }; - }, - currentSlot() { - return UInt32.from( - Math.ceil((new Date().valueOf() - startTime) / slotTime) - ); - }, - hasAccount(publicKey: PublicKey, tokenId: Field = TokenId.default) { - return !!ledger.getAccount( - Ml.fromPublicKey(publicKey), - Ml.constFromField(tokenId) - ); - }, - getAccount( - publicKey: PublicKey, - tokenId: Field = TokenId.default - ): Account { - let accountJson = ledger.getAccount( - Ml.fromPublicKey(publicKey), - Ml.constFromField(tokenId) - ); - if (accountJson === undefined) { - throw new Error( - reportGetAccountError(publicKey.toBase58(), TokenId.toBase58(tokenId)) - ); - } - return Types.Account.fromJSON(accountJson); - }, - getNetworkState() { - return networkState; - }, - async sendTransaction(txn: Transaction): Promise { - txn.sign(); - - let zkappCommandJson = ZkappCommand.toJSON(txn.transaction); - let commitments = transactionCommitments( - TypesBigint.ZkappCommand.fromJSON(zkappCommandJson) - ); - - if (enforceTransactionLimits) verifyTransactionLimits(txn.transaction); - - for (const update of txn.transaction.accountUpdates) { - let accountJson = ledger.getAccount( - Ml.fromPublicKey(update.body.publicKey), - Ml.constFromField(update.body.tokenId) - ); - if (accountJson) { - let account = Account.fromJSON(accountJson); - await verifyAccountUpdate( - account, - update, - commitments, - proofsEnabled - ); - } - } - - try { - ledger.applyJsonTransaction( - JSON.stringify(zkappCommandJson), - String(accountCreationFee), - JSON.stringify(networkState) - ); - } catch (err: any) { - try { - // reverse errors so they match order of account updates - // TODO: label updates, and try to give precise explanations about what went wrong - let errors = JSON.parse(err.message); - err.message = invalidTransactionError(txn.transaction, errors, { - accountCreationFee, - }); - } finally { - throw err; - } - } - - // fetches all events from the transaction and stores them - // events are identified and associated with a publicKey and tokenId - txn.transaction.accountUpdates.forEach((p, i) => { - let pJson = zkappCommandJson.accountUpdates[i]; - let addr = pJson.body.publicKey; - let tokenId = pJson.body.tokenId; - events[addr] ??= {}; - if (p.body.events.data.length > 0) { - events[addr][tokenId] ??= []; - let updatedEvents = p.body.events.data.map((data) => { - return { - data, - transactionInfo: { - transactionHash: '', - transactionStatus: '', - transactionMemo: '', - }, - }; - }); - events[addr][tokenId].push({ - events: updatedEvents, - blockHeight: networkState.blockchainLength, - globalSlot: networkState.globalSlotSinceGenesis, - // The following fields are fetched from the Mina network. For now, we mock these values out - // since networkState does not contain these fields. - blockHash: '', - parentBlockHash: '', - chainStatus: '', - }); - } - - // actions/sequencing events - - // most recent action state - let storedActions = actions[addr]?.[tokenId]; - let latestActionState_ = - storedActions?.[storedActions.length - 1]?.hash; - // if there exists no hash, this means we initialize our latest hash with the empty state - let latestActionState = - latestActionState_ !== undefined - ? Field(latestActionState_) - : Actions.emptyActionState(); - - actions[addr] ??= {}; - if (p.body.actions.data.length > 0) { - let newActionState = Actions.updateSequenceState( - latestActionState, - p.body.actions.hash - ); - actions[addr][tokenId] ??= []; - actions[addr][tokenId].push({ - actions: pJson.body.actions, - hash: newActionState.toString(), - }); - } - }); - return { - isSuccess: true, - wait: async (_options?: { - maxAttempts?: number; - interval?: number; - }) => { - console.log( - 'Info: Waiting for inclusion in a block is not supported for LocalBlockchain.' - ); - }, - hash: (): string => { - const message = - 'Info: Txn Hash retrieving is not supported for LocalBlockchain.'; - console.log(message); - return message; - }, - }; - }, - async transaction(sender: DeprecatedFeePayerSpec, f: () => void) { - // bad hack: run transaction just to see whether it creates proofs - // if it doesn't, this is the last chance to run SmartContract.runOutsideCircuit, which is supposed to run only once - // TODO: this has obvious holes if multiple zkapps are involved, but not relevant currently because we can't prove with multiple account updates - // and hopefully with upcoming work by Matt we can just run everything in the prover, and nowhere else - let tx = createTransaction(sender, f, 0, { - isFinalRunOutsideCircuit: false, - proofsEnabled, - fetchMode: 'test', - }); - let hasProofs = tx.transaction.accountUpdates.some( - Authorization.hasLazyProof - ); - return createTransaction(sender, f, 1, { - isFinalRunOutsideCircuit: !hasProofs, - proofsEnabled, - }); - }, - applyJsonTransaction(json: string) { - return ledger.applyJsonTransaction( - json, - String(accountCreationFee), - JSON.stringify(networkState) - ); - }, - async fetchEvents(publicKey: PublicKey, tokenId: Field = TokenId.default) { - return events?.[publicKey.toBase58()]?.[TokenId.toBase58(tokenId)] ?? []; - }, - async fetchActions( - publicKey: PublicKey, - actionStates?: ActionStates, - tokenId: Field = TokenId.default - ) { - return this.getActions(publicKey, actionStates, tokenId); - }, - getActions( - publicKey: PublicKey, - actionStates?: ActionStates, - tokenId: Field = TokenId.default - ): { hash: string; actions: string[][] }[] { - let currentActions = - actions?.[publicKey.toBase58()]?.[TokenId.toBase58(tokenId)] ?? []; - let { fromActionState, endActionState } = actionStates ?? {}; - - let emptyState = Actions.emptyActionState(); - if (endActionState?.equals(emptyState).toBoolean()) return []; - - let start = fromActionState?.equals(emptyState).toBoolean() - ? undefined - : fromActionState?.toString(); - let end = endActionState?.toString(); - - let startIndex = 0; - if (start) { - let i = currentActions.findIndex((e) => e.hash === start); - if (i === -1) throw Error(`getActions: fromActionState not found.`); - startIndex = i + 1; - } - let endIndex: number | undefined; - if (end) { - let i = currentActions.findIndex((e) => e.hash === end); - if (i === -1) throw Error(`getActions: endActionState not found.`); - endIndex = i + 1; - } - return currentActions.slice(startIndex, endIndex); - }, - addAccount, - /** - * An array of 10 test accounts that have been pre-filled with - * 30000000000 units of currency. - */ - testAccounts, - setGlobalSlot(slot: UInt32 | number) { - networkState.globalSlotSinceGenesis = UInt32.from(slot); - }, - incrementGlobalSlot(increment: UInt32 | number) { - networkState.globalSlotSinceGenesis = - networkState.globalSlotSinceGenesis.add(increment); - }, - setBlockchainLength(height: UInt32) { - networkState.blockchainLength = height; - }, - setTotalCurrency(currency: UInt64) { - networkState.totalCurrency = currency; - }, - setProofsEnabled(newProofsEnabled: boolean) { - proofsEnabled = newProofsEnabled; - }, - }; -} -// assert type compatibility without preventing LocalBlockchain to return additional properties / methods -LocalBlockchain satisfies (...args: any) => Mina; - -/** - * Represents the Mina blockchain running on a real network - */ -function Network(graphqlEndpoint: string): Mina; -function Network(graphqlEndpoints: { - mina: string | string[]; - archive: string | string[]; -}): Mina; -function Network( - input: { mina: string | string[]; archive: string | string[] } | string -): Mina { - let accountCreationFee = UInt64.from(defaultAccountCreationFee); - let minaGraphqlEndpoint: string; - let archiveEndpoint: string; - - if (input && typeof input === 'string') { - minaGraphqlEndpoint = input; - Fetch.setGraphqlEndpoint(minaGraphqlEndpoint); - } else if (input && typeof input === 'object') { - if (!input.mina || !input.archive) - throw new Error( - "Network: malformed input. Please provide an object with 'mina' and 'archive' endpoints." - ); - if (Array.isArray(input.mina) && input.mina.length !== 0) { - minaGraphqlEndpoint = input.mina[0]; - Fetch.setGraphqlEndpoint(minaGraphqlEndpoint); - Fetch.setMinaGraphqlFallbackEndpoints(input.mina.slice(1)); - } else if (typeof input.mina === 'string') { - minaGraphqlEndpoint = input.mina; - Fetch.setGraphqlEndpoint(minaGraphqlEndpoint); - } - - if (Array.isArray(input.archive) && input.archive.length !== 0) { - archiveEndpoint = input.archive[0]; - Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); - Fetch.setArchiveGraphqlFallbackEndpoints(input.archive.slice(1)); - } else if (typeof input.archive === 'string') { - archiveEndpoint = input.archive; - Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); - } - } else { - throw new Error( - "Network: malformed input. Please provide a string or an object with 'mina' and 'archive' endpoints." - ); - } - - // copied from mina/genesis_ledgers/berkeley.json - // TODO fetch from graphql instead of hardcoding - const genesisTimestampString = '2023-02-23T20:00:01Z'; - const genesisTimestamp = UInt64.from( - Date.parse(genesisTimestampString.slice(0, -1) + '+00:00') - ); - // TODO also fetch from graphql - const slotTime = UInt64.from(3 * 60 * 1000); - return { - accountCreationFee: () => accountCreationFee, - getNetworkConstants() { - return { - genesisTimestamp, - slotTime, - accountCreationFee, - }; - }, - currentSlot() { - throw Error( - 'currentSlot() is not implemented yet for remote blockchains.' - ); - }, - hasAccount(publicKey: PublicKey, tokenId: Field = TokenId.default) { - if ( - !currentTransaction.has() || - currentTransaction.get().fetchMode === 'cached' - ) { - return !!Fetch.getCachedAccount( - publicKey, - tokenId, - minaGraphqlEndpoint - ); - } - return false; - }, - getAccount(publicKey: PublicKey, tokenId: Field = TokenId.default) { - if (currentTransaction()?.fetchMode === 'test') { - Fetch.markAccountToBeFetched(publicKey, tokenId, minaGraphqlEndpoint); - let account = Fetch.getCachedAccount( - publicKey, - tokenId, - minaGraphqlEndpoint - ); - return account ?? dummyAccount(publicKey); - } - if ( - !currentTransaction.has() || - currentTransaction.get().fetchMode === 'cached' - ) { - let account = Fetch.getCachedAccount( - publicKey, - tokenId, - minaGraphqlEndpoint - ); - if (account !== undefined) return account; - } - throw Error( - `${reportGetAccountError( - publicKey.toBase58(), - TokenId.toBase58(tokenId) - )}\nGraphql endpoint: ${minaGraphqlEndpoint}` - ); - }, - getNetworkState() { - if (currentTransaction()?.fetchMode === 'test') { - Fetch.markNetworkToBeFetched(minaGraphqlEndpoint); - let network = Fetch.getCachedNetwork(minaGraphqlEndpoint); - return network ?? defaultNetworkState(); - } - if ( - !currentTransaction.has() || - currentTransaction.get().fetchMode === 'cached' - ) { - let network = Fetch.getCachedNetwork(minaGraphqlEndpoint); - if (network !== undefined) return network; - } - throw Error( - `getNetworkState: Could not fetch network state from graphql endpoint ${minaGraphqlEndpoint}` - ); - }, - async sendTransaction(txn: Transaction) { - txn.sign(); - - verifyTransactionLimits(txn.transaction); - - let [response, error] = await Fetch.sendZkapp(txn.toJSON()); - let errors: any[] | undefined; - if (response === undefined && error !== undefined) { - console.log('Error: Failed to send transaction', error); - errors = [error]; - } else if (response && response.errors && response.errors.length > 0) { - console.log( - 'Error: Transaction returned with errors', - JSON.stringify(response.errors, null, 2) - ); - errors = response.errors; - } - - let isSuccess = errors === undefined; - let maxAttempts: number; - let attempts = 0; - let interval: number; - - return { - isSuccess, - data: response?.data, - errors, - async wait(options?: { maxAttempts?: number; interval?: number }) { - if (!isSuccess) { - console.warn( - 'Transaction.wait(): returning immediately because the transaction was not successful.' - ); - return; - } - // default is 45 attempts * 20s each = 15min - // the block time on berkeley is currently longer than the average 3-4min, so its better to target a higher block time - // fetching an update every 20s is more than enough with a current block time of 3min - maxAttempts = options?.maxAttempts ?? 45; - interval = options?.interval ?? 20000; - - const executePoll = async ( - resolve: () => void, - reject: (err: Error) => void | Error - ) => { - let txId = response?.data?.sendZkapp?.zkapp?.hash; - let res; - try { - res = await Fetch.checkZkappTransaction(txId); - } catch (error) { - isSuccess = false; - return reject(error as Error); - } - attempts++; - if (res.success) { - isSuccess = true; - return resolve(); - } else if (res.failureReason) { - isSuccess = false; - return reject( - new Error( - `Transaction failed.\nTransactionId: ${txId}\nAttempts: ${attempts}\nfailureReason(s): ${res.failureReason}` - ) - ); - } else if (maxAttempts && attempts === maxAttempts) { - isSuccess = false; - return reject( - new Error( - `Exceeded max attempts.\nTransactionId: ${txId}\nAttempts: ${attempts}\nLast received status: ${res}` - ) - ); - } else { - setTimeout(executePoll, interval, resolve, reject); - } - }; - - return new Promise(executePoll); - }, - hash() { - return response?.data?.sendZkapp?.zkapp?.hash; - }, - }; - }, - async transaction(sender: DeprecatedFeePayerSpec, f: () => void) { - let tx = createTransaction(sender, f, 0, { - fetchMode: 'test', - isFinalRunOutsideCircuit: false, - }); - await Fetch.fetchMissingData(minaGraphqlEndpoint, archiveEndpoint); - let hasProofs = tx.transaction.accountUpdates.some( - Authorization.hasLazyProof - ); - return createTransaction(sender, f, 1, { - fetchMode: 'cached', - isFinalRunOutsideCircuit: !hasProofs, - }); - }, - async fetchEvents( - publicKey: PublicKey, - tokenId: Field = TokenId.default, - filterOptions: Fetch.EventActionFilterOptions = {} - ) { - let pubKey = publicKey.toBase58(); - let token = TokenId.toBase58(tokenId); - - return Fetch.fetchEvents( - { publicKey: pubKey, tokenId: token }, - archiveEndpoint, - filterOptions - ); - }, - async fetchActions( - publicKey: PublicKey, - actionStates?: ActionStates, - tokenId: Field = TokenId.default - ) { - let pubKey = publicKey.toBase58(); - let token = TokenId.toBase58(tokenId); - let { fromActionState, endActionState } = actionStates ?? {}; - let fromActionStateBase58 = fromActionState - ? fromActionState.toString() - : undefined; - let endActionStateBase58 = endActionState - ? endActionState.toString() - : undefined; - - return Fetch.fetchActions( - { - publicKey: pubKey, - actionStates: { - fromActionState: fromActionStateBase58, - endActionState: endActionStateBase58, - }, - tokenId: token, - }, - archiveEndpoint - ); - }, - getActions( - publicKey: PublicKey, - actionStates?: ActionStates, - tokenId: Field = TokenId.default - ) { - if (currentTransaction()?.fetchMode === 'test') { - Fetch.markActionsToBeFetched( - publicKey, - tokenId, - archiveEndpoint, - actionStates - ); - let actions = Fetch.getCachedActions(publicKey, tokenId); - return actions ?? []; - } - if ( - !currentTransaction.has() || - currentTransaction.get().fetchMode === 'cached' - ) { - let actions = Fetch.getCachedActions(publicKey, tokenId); - if (actions !== undefined) return actions; - } - throw Error( - `getActions: Could not find actions for the public key ${publicKey}` - ); - }, - proofsEnabled: true, - }; -} - -/** - * - * @deprecated This is deprecated in favor of {@link Mina.Network}, which is exactly the same function. - * The name `BerkeleyQANet` was misleading because it suggested that this is specific to a particular network. - */ -function BerkeleyQANet(graphqlEndpoint: string) { - return Network(graphqlEndpoint); -} - -let activeInstance: Mina = { - accountCreationFee: () => UInt64.from(defaultAccountCreationFee), - getNetworkConstants() { - throw new Error('must call Mina.setActiveInstance first'); - }, - currentSlot: () => { - throw new Error('must call Mina.setActiveInstance first'); - }, - hasAccount(publicKey: PublicKey, tokenId: Field = TokenId.default) { - if ( - !currentTransaction.has() || - currentTransaction.get().fetchMode === 'cached' - ) { - return !!Fetch.getCachedAccount( - publicKey, - tokenId, - Fetch.networkConfig.minaEndpoint - ); - } - return false; - }, - getAccount(publicKey: PublicKey, tokenId: Field = TokenId.default) { - if (currentTransaction()?.fetchMode === 'test') { - Fetch.markAccountToBeFetched( - publicKey, - tokenId, - Fetch.networkConfig.minaEndpoint - ); - return dummyAccount(publicKey); - } - if ( - !currentTransaction.has() || - currentTransaction.get().fetchMode === 'cached' - ) { - let account = Fetch.getCachedAccount( - publicKey, - tokenId, - Fetch.networkConfig.minaEndpoint - ); - if (account === undefined) - throw Error( - `${reportGetAccountError( - publicKey.toBase58(), - TokenId.toBase58(tokenId) - )}\n\nEither call Mina.setActiveInstance first or explicitly add the account with addCachedAccount` - ); - return account; - } - throw new Error('must call Mina.setActiveInstance first'); - }, - getNetworkState() { - throw new Error('must call Mina.setActiveInstance first'); - }, - sendTransaction() { - throw new Error('must call Mina.setActiveInstance first'); - }, - async transaction(sender: DeprecatedFeePayerSpec, f: () => void) { - return createTransaction(sender, f, 0); - }, - fetchEvents(_publicKey: PublicKey, _tokenId: Field = TokenId.default) { - throw Error('must call Mina.setActiveInstance first'); - }, - fetchActions( - _publicKey: PublicKey, - _actionStates?: ActionStates, - _tokenId: Field = TokenId.default - ) { - throw Error('must call Mina.setActiveInstance first'); - }, - getActions( - _publicKey: PublicKey, - _actionStates?: ActionStates, - _tokenId: Field = TokenId.default - ) { - throw Error('must call Mina.setActiveInstance first'); - }, - proofsEnabled: true, -}; - -/** - * Set the currently used Mina instance. - */ -function setActiveInstance(m: Mina) { - activeInstance = m; -} - -/** - * Construct a smart contract transaction. Within the callback passed to this function, - * you can call into the methods of smart contracts. - * - * ``` - * let tx = await Mina.transaction(sender, () => { - * myZkapp.update(); - * someOtherZkapp.someOtherMethod(); - * }); - * ``` - * - * @return A transaction that can subsequently be submitted to the chain. - */ -function transaction(sender: FeePayerSpec, f: () => void): Promise; -function transaction(f: () => void): Promise; -/** - * @deprecated It's deprecated to pass in the fee payer's private key. Pass in the public key instead. - * ``` - * // good - * Mina.transaction(publicKey, ...); - * Mina.transaction({ sender: publicKey }, ...); - * - * // deprecated - * Mina.transaction(privateKey, ...); - * Mina.transaction({ feePayerKey: privateKey }, ...); - * ``` - */ -function transaction( - sender: DeprecatedFeePayerSpec, - f: () => void -): Promise; -function transaction( - senderOrF: DeprecatedFeePayerSpec | (() => void), - fOrUndefined?: () => void -): Promise { - let sender: DeprecatedFeePayerSpec; - let f: () => void; - try { - if (fOrUndefined !== undefined) { - sender = senderOrF as DeprecatedFeePayerSpec; - f = fOrUndefined; - } else { - sender = undefined; - f = senderOrF as () => void; - } - return activeInstance.transaction(sender, f); - } catch (error) { - throw prettifyStacktrace(error); - } -} - -/** - * Returns the public key of the current transaction's sender account. - * - * Throws an error if not inside a transaction, or the sender wasn't passed in. - */ -function sender() { - let tx = currentTransaction(); - if (tx === undefined) - throw Error( - `The sender is not available outside a transaction. Make sure you only use it within \`Mina.transaction\` blocks or smart contract methods.` - ); - let sender = currentTransaction()?.sender; - if (sender === undefined) - throw Error( - `The sender is not available, because the transaction block was created without the optional \`sender\` argument. -Here's an example for how to pass in the sender and make it available: - -Mina.transaction(sender, // <-- pass in sender's public key here -() => { - // methods can use this.sender -}); -` - ); - return sender; -} - -/** - * @return The current slot number, according to the active Mina instance. - */ -function currentSlot(): UInt32 { - return activeInstance.currentSlot(); -} - -/** - * @return The account data associated to the given public key. - */ -function getAccount(publicKey: PublicKey, tokenId?: Field): Account { - return activeInstance.getAccount(publicKey, tokenId); -} - -/** - * Checks if an account exists within the ledger. - */ -function hasAccount(publicKey: PublicKey, tokenId?: Field): boolean { - return activeInstance.hasAccount(publicKey, tokenId); -} - -/** - * @return Data associated with the current state of the Mina network. - */ -function getNetworkState() { - return activeInstance.getNetworkState(); -} - -/** - * @return The balance associated to the given public key. - */ -function getBalance(publicKey: PublicKey, tokenId?: Field) { - return activeInstance.getAccount(publicKey, tokenId).balance; -} - -/** - * Returns the default account creation fee. - */ -function accountCreationFee() { - return activeInstance.accountCreationFee(); -} - -async function sendTransaction(txn: Transaction) { - return await activeInstance.sendTransaction(txn); -} - -/** - * @return A list of emitted events associated to the given public key. - */ -async function fetchEvents( - publicKey: PublicKey, - tokenId: Field, - filterOptions: Fetch.EventActionFilterOptions = {} -) { - return await activeInstance.fetchEvents(publicKey, tokenId, filterOptions); -} - -/** - * @return A list of emitted sequencing actions associated to the given public key. - */ -async function fetchActions( - publicKey: PublicKey, - actionStates?: ActionStates, - tokenId?: Field -) { - return await activeInstance.fetchActions(publicKey, actionStates, tokenId); -} - -/** - * @return A list of emitted sequencing actions associated to the given public key. - */ -function getActions( - publicKey: PublicKey, - actionStates?: ActionStates, - tokenId?: Field -) { - return activeInstance.getActions(publicKey, actionStates, tokenId); -} - -function getProofsEnabled() { - return activeInstance.proofsEnabled; -} - -function dummyAccount(pubkey?: PublicKey): Account { - let dummy = Types.Account.emptyValue(); - if (pubkey) dummy.publicKey = pubkey; - return dummy; -} - -function defaultNetworkState(): NetworkValue { - let epochData: NetworkValue['stakingEpochData'] = { - ledger: { hash: Field(0), totalCurrency: UInt64.zero }, - seed: Field(0), - startCheckpoint: Field(0), - lockCheckpoint: Field(0), - epochLength: UInt32.zero, - }; - return { - snarkedLedgerHash: Field(0), - blockchainLength: UInt32.zero, - minWindowDensity: UInt32.zero, - totalCurrency: UInt64.zero, - globalSlotSinceGenesis: UInt32.zero, - stakingEpochData: epochData, - nextEpochData: cloneCircuitValue(epochData), - }; -} - -async function verifyAccountUpdate( - account: Account, - accountUpdate: AccountUpdate, - transactionCommitments: { commitment: bigint; fullCommitment: bigint }, - proofsEnabled: boolean -): Promise { - // check that that top-level updates have mayUseToken = No - // (equivalent check exists in the Mina node) - if ( - accountUpdate.body.callDepth === 0 && - !AccountUpdate.MayUseToken.isNo(accountUpdate).toBoolean() - ) { - throw Error( - 'Top-level account update can not use or pass on token permissions. Make sure that\n' + - 'accountUpdate.body.mayUseToken = AccountUpdate.MayUseToken.No;' - ); - } - - let perm = account.permissions; - - // check if addMissingSignatures failed to include a signature - // due to a missing private key - if (accountUpdate.authorization === dummySignature()) { - let pk = PublicKey.toBase58(accountUpdate.body.publicKey); - throw Error( - `verifyAccountUpdate: Detected a missing signature for (${pk}), private key was missing.` - ); - } - // we are essentially only checking if the update is empty or an actual update - function includesChange( - val: T | string | null | (string | null)[] - ): boolean { - if (Array.isArray(val)) { - return !val.every((v) => v === null); - } else { - return val !== null; - } - } - - function permissionForUpdate(key: string): Types.AuthRequired { - switch (key) { - case 'appState': - return perm.editState; - case 'delegate': - return perm.setDelegate; - case 'verificationKey': - return perm.setVerificationKey.auth; - case 'permissions': - return perm.setPermissions; - case 'zkappUri': - return perm.setZkappUri; - case 'tokenSymbol': - return perm.setTokenSymbol; - case 'timing': - return perm.setTiming; - case 'votingFor': - return perm.setVotingFor; - case 'actions': - return perm.editActionState; - case 'incrementNonce': - return perm.incrementNonce; - case 'send': - return perm.send; - case 'receive': - return perm.receive; - default: - throw Error(`Invalid permission for field ${key}: does not exist.`); - } - } - - let accountUpdateJson = accountUpdate.toJSON(); - const update = accountUpdateJson.body.update; - - let errorTrace = ''; - - let isValidProof = false; - let isValidSignature = false; - - // we don't check if proofs aren't enabled - if (!proofsEnabled) isValidProof = true; - - if (accountUpdate.authorization.proof && proofsEnabled) { - try { - let publicInput = accountUpdate.toPublicInput(); - let publicInputFields = ZkappPublicInput.toFields(publicInput); - - const proof = SmartContract.Proof().fromJSON({ - maxProofsVerified: 2, - proof: accountUpdate.authorization.proof!, - publicInput: publicInputFields.map((f) => f.toString()), - publicOutput: [], - }); - - let verificationKey = account.zkapp?.verificationKey?.data!; - isValidProof = await verify(proof.toJSON(), verificationKey); - if (!isValidProof) { - throw Error( - `Invalid proof for account update\n${JSON.stringify(update)}` - ); - } - } catch (error) { - errorTrace += '\n\n' + (error as Error).message; - isValidProof = false; - } - } - - if (accountUpdate.authorization.signature) { - // checking permissions and authorization for each account update individually - try { - isValidSignature = verifyAccountUpdateSignature( - TypesBigint.AccountUpdate.fromJSON(accountUpdateJson), - transactionCommitments, - 'testnet' - ); - } catch (error) { - errorTrace += '\n\n' + (error as Error).message; - isValidSignature = false; - } - } - - let verified = false; - - function checkPermission(p0: Types.AuthRequired, field: string) { - let p = Types.AuthRequired.toJSON(p0); - if (p === 'None') return; - - if (p === 'Impossible') { - throw Error( - `Transaction verification failed: Cannot update field '${field}' because permission for this field is '${p}'` - ); - } - - if (p === 'Signature' || p === 'Either') { - verified ||= isValidSignature; - } - - if (p === 'Proof' || p === 'Either') { - verified ||= isValidProof; - } - - if (!verified) { - throw Error( - `Transaction verification failed: Cannot update field '${field}' because permission for this field is '${p}', but the required authorization was not provided or is invalid. - ${errorTrace !== '' ? 'Error trace: ' + errorTrace : ''}` - ); - } - } - - // goes through the update field on a transaction - Object.entries(update).forEach(([key, value]) => { - if (includesChange(value)) { - let p = permissionForUpdate(key); - checkPermission(p, key); - } - }); - - // checks the sequence events (which result in an updated sequence state) - if (accountUpdate.body.actions.data.length > 0) { - let p = permissionForUpdate('actions'); - checkPermission(p, 'actions'); - } - - if (accountUpdate.body.incrementNonce.toBoolean()) { - let p = permissionForUpdate('incrementNonce'); - checkPermission(p, 'incrementNonce'); - } - - // this checks for an edge case where an account update can be approved using proofs but - // a) the proof is invalid (bad verification key) - // and b) there are no state changes initiate so no permissions will be checked - // however, if the verification key changes, the proof should still be invalid - if (errorTrace && !verified) { - throw Error( - `One or more proofs were invalid and no other form of authorization was provided.\n${errorTrace}` - ); - } -} - -function verifyTransactionLimits({ accountUpdates }: ZkappCommand) { - let eventElements = { events: 0, actions: 0 }; - - let authKinds = accountUpdates.map((update) => { - eventElements.events += countEventElements(update.body.events); - eventElements.actions += countEventElements(update.body.actions); - let { isSigned, isProved, verificationKeyHash } = - update.body.authorizationKind; - return { - isSigned: isSigned.toBoolean(), - isProved: isProved.toBoolean(), - verificationKeyHash: verificationKeyHash.toString(), - }; - }); - // insert entry for the fee payer - authKinds.unshift({ - isSigned: true, - isProved: false, - verificationKeyHash: '', - }); - let authTypes = filterGroups(authKinds); - - /* - np := proof - n2 := signedPair - n1 := signedSingle - - formula used to calculate how expensive a zkapp transaction is - - 10.26*np + 10.08*n2 + 9.14*n1 < 69.45 - */ - let totalTimeRequired = - TransactionCost.PROOF_COST * authTypes.proof + - TransactionCost.SIGNED_PAIR_COST * authTypes.signedPair + - TransactionCost.SIGNED_SINGLE_COST * authTypes.signedSingle; - - let isWithinCostLimit = totalTimeRequired < TransactionCost.COST_LIMIT; - - let isWithinEventsLimit = - eventElements.events <= TransactionLimits.MAX_EVENT_ELEMENTS; - let isWithinActionsLimit = - eventElements.actions <= TransactionLimits.MAX_ACTION_ELEMENTS; - - let error = ''; - - if (!isWithinCostLimit) { - // TODO: we should add a link to the docs explaining the reasoning behind it once we have such an explainer - error += `Error: The transaction is too expensive, try reducing the number of AccountUpdates that are attached to the transaction. -Each transaction needs to be processed by the snark workers on the network. -Certain layouts of AccountUpdates require more proving time than others, and therefore are too expensive. - -${JSON.stringify(authTypes)} -\n\n`; - } - - if (!isWithinEventsLimit) { - error += `Error: The account updates in your transaction are trying to emit too much event data. The maximum allowed number of field elements in events is ${TransactionLimits.MAX_EVENT_ELEMENTS}, but you tried to emit ${eventElements.events}.\n\n`; - } - - if (!isWithinActionsLimit) { - error += `Error: The account updates in your transaction are trying to emit too much action data. The maximum allowed number of field elements in actions is ${TransactionLimits.MAX_ACTION_ELEMENTS}, but you tried to emit ${eventElements.actions}.\n\n`; - } - - if (error) throw Error('Error during transaction sending:\n\n' + error); -} - -function countEventElements({ data }: Events) { - return data.reduce((acc, ev) => acc + ev.length, 0); -} - -type AuthorizationKind = { isProved: boolean; isSigned: boolean }; - -const isPair = (a: AuthorizationKind, b: AuthorizationKind) => - !a.isProved && !b.isProved; - -function filterPairs(xs: AuthorizationKind[]): { - xs: { isProved: boolean; isSigned: boolean }[]; - pairs: number; -} { - if (xs.length <= 1) return { xs, pairs: 0 }; - if (isPair(xs[0], xs[1])) { - let rec = filterPairs(xs.slice(2)); - return { xs: rec.xs, pairs: rec.pairs + 1 }; - } else { - let rec = filterPairs(xs.slice(1)); - return { xs: [xs[0]].concat(rec.xs), pairs: rec.pairs }; - } -} - -function filterGroups(xs: AuthorizationKind[]) { - let pairs = filterPairs(xs); - xs = pairs.xs; - - let singleCount = 0; - let proofCount = 0; - - xs.forEach((t) => { - if (t.isProved) proofCount++; - else singleCount++; - }); - - return { - signedPair: pairs.pairs, - signedSingle: singleCount, - proof: proofCount, - }; -} - -async function waitForFunding(address: string): Promise { - let attempts = 0; - let maxAttempts = 30; - let interval = 30000; - const executePoll = async ( - resolve: () => void, - reject: (err: Error) => void | Error - ) => { - let { account } = await Fetch.fetchAccount({ publicKey: address }); - attempts++; - if (account) { - return resolve(); - } else if (maxAttempts && attempts === maxAttempts) { - return reject(new Error(`Exceeded max attempts`)); - } else { - setTimeout(executePoll, interval, resolve, reject); - } - }; - return new Promise(executePoll); -} - -/** - * Requests the [testnet faucet](https://faucet.minaprotocol.com/api/v1/faucet) to fund a public key. - */ -async function faucet(pub: PublicKey, network: string = 'berkeley-qanet') { - let address = pub.toBase58(); - let response = await fetch('https://faucet.minaprotocol.com/api/v1/faucet', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - network, - address: address, - }), - }); - response = await response.json(); - if (response.status.toString() !== 'success') { - throw new Error( - `Error funding account ${address}, got response status: ${response.status}, text: ${response.statusText}` - ); - } - await waitForFunding(address); -} diff --git a/src/lib/mina/account-update-layout.unit-test.ts b/src/lib/mina/account-update-layout.unit-test.ts new file mode 100644 index 0000000000..2cce42f4b7 --- /dev/null +++ b/src/lib/mina/account-update-layout.unit-test.ts @@ -0,0 +1,66 @@ +import { Mina } from '../../index.js'; +import { AccountUpdate, AccountUpdateTree } from './account-update.js'; +import { UInt64 } from '../provable/int.js'; +import { SmartContract, method } from './zkapp.js'; + +// smart contract which creates an account update that has a child of its own + +class NestedCall extends SmartContract { + @method async deposit() { + let sender = this.sender.getUnconstrained(); + let payerUpdate = AccountUpdate.createSigned(sender); + payerUpdate.send({ to: this.address, amount: UInt64.one }); + } + + @method async depositUsingTree() { + let sender = this.sender.getUnconstrained(); + let payerUpdate = AccountUpdate.createSigned(sender); + let receiverUpdate = AccountUpdate.create(this.address); + payerUpdate.send({ to: receiverUpdate, amount: UInt64.one }); + + let tree = AccountUpdateTree.from(payerUpdate); + tree.approve(receiverUpdate); + this.approve(tree); + } +} + +// setup + +let Local = Mina.LocalBlockchain({ proofsEnabled: true }); +Mina.setActiveInstance(Local); + +let [ + { publicKey: sender, privateKey: senderKey }, + { publicKey: zkappAddress, privateKey: zkappKey }, +] = Local.testAccounts; + +await NestedCall.compile(); +let zkapp = new NestedCall(zkappAddress); + +// deploy zkapp + +await (await Mina.transaction(sender, () => zkapp.deploy())) + .sign([zkappKey, senderKey]) + .send(); + +// deposit call + +let balanceBefore = Mina.getBalance(zkappAddress); + +let depositTx = await Mina.transaction(sender, () => zkapp.deposit()); +console.log(depositTx.toPretty()); +await depositTx.prove(); +await depositTx.sign([senderKey]).send(); + +Mina.getBalance(zkappAddress).assertEquals(balanceBefore.add(1)); + +// deposit call using tree + +balanceBefore = balanceBefore.add(1); + +depositTx = await Mina.transaction(sender, () => zkapp.depositUsingTree()); +console.log(depositTx.toPretty()); +await depositTx.prove(); +await depositTx.sign([senderKey]).send(); + +Mina.getBalance(zkappAddress).assertEquals(balanceBefore.add(1)); diff --git a/src/lib/account_update.ts b/src/lib/mina/account-update.ts similarity index 58% rename from src/lib/account_update.ts rename to src/lib/mina/account-update.ts index ac94a8f091..faf9ef4e53 100644 --- a/src/lib/account_update.ts +++ b/src/lib/mina/account-update.ts @@ -3,46 +3,98 @@ import { FlexibleProvable, provable, provablePure, -} from './circuit_value.js'; -import { memoizationContext, memoizeWitness, Provable } from './provable.js'; -import { Field, Bool } from './core.js'; -import { Pickles, Test } from '../snarky.js'; -import { jsLayout } from '../bindings/mina-transaction/gen/js-layout.js'; + StructNoJson, +} from '../provable/types/struct.js'; +import { + memoizationContext, + memoizeWitness, + Provable, +} from '../provable/provable.js'; +import { Field, Bool } from '../provable/wrapped.js'; +import { Pickles, Test } from '../../snarky.js'; +import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; import { Types, TypesBigint, toJSONEssential, -} from '../bindings/mina-transaction/types.js'; -import { PrivateKey, PublicKey } from './signature.js'; -import { UInt64, UInt32, Int64, Sign } from './int.js'; -import * as Mina from './mina.js'; -import { SmartContract } from './zkapp.js'; -import * as Precondition from './precondition.js'; -import { dummyBase64Proof, Empty, Proof, Prover } from './proof_system.js'; -import { Memo } from '../mina-signer/src/memo.js'; +} from '../../bindings/mina-transaction/types.js'; +import { PrivateKey, PublicKey } from '../provable/crypto/signature.js'; +import { UInt64, UInt32, Int64, Sign } from '../provable/int.js'; +import type { SmartContract } from './zkapp.js'; +import { + Preconditions, + Account, + Network, + CurrentSlot, + preconditions, + OrIgnore, + ClosedInterval, + getAccountPreconditions, +} from './precondition.js'; +import { + dummyBase64Proof, + Empty, + Proof, + Prover, +} from '../proof-system/zkprogram.js'; +import { Memo } from '../../mina-signer/src/memo.js'; import { Events, Actions, -} from '../bindings/mina-transaction/transaction-leaves.js'; +} from '../../bindings/mina-transaction/transaction-leaves.js'; import { TokenId as Base58TokenId } from './base58-encodings.js'; -import { hashWithPrefix, packToFields } from './hash.js'; +import { + hashWithPrefix, + packToFields, + Poseidon, +} from '../provable/crypto/poseidon.js'; import { mocks, prefixes, protocolVersions, -} from '../bindings/crypto/constants.js'; -import { Context } from './global-context.js'; -import { assert } from './errors.js'; -import { MlArray } from './ml/base.js'; -import { Signature, signFieldElement } from '../mina-signer/src/signature.js'; -import { MlFieldConstArray } from './ml/fields.js'; -import { transactionCommitments } from '../mina-signer/src/sign-zkapp-command.js'; +} from '../../bindings/crypto/constants.js'; +import { MlArray } from '../ml/base.js'; +import { + Signature, + signFieldElement, + zkAppBodyPrefix, +} from '../../mina-signer/src/signature.js'; +import { MlFieldConstArray } from '../ml/fields.js'; +import { + accountUpdatesToCallForest, + CallForest, + callForestHashGeneric, + transactionCommitments, +} from '../../mina-signer/src/sign-zkapp-command.js'; +import { currentTransaction } from './transaction-context.js'; +import { isSmartContract } from './smart-contract-base.js'; +import { activeInstance } from './mina-instance.js'; +import { + emptyHash, + genericHash, + MerkleList, + MerkleListBase, +} from '../provable/merkle-list.js'; +import { Hashed } from '../provable/packed.js'; +import { + accountUpdateLayout, + smartContractContext, +} from './smart-contract-context.js'; +import { assert } from '../util/assert.js'; +import { RandomId } from '../provable/types/auxiliary.js'; +import { NetworkId } from '../../mina-signer/src/types.js'; // external API -export { AccountUpdate, Permissions, ZkappPublicInput, TransactionVersion }; +export { + AccountUpdate, + Permissions, + ZkappPublicInput, + TransactionVersion, + AccountUpdateForest, + AccountUpdateTree, +}; // internal API export { - smartContractContext, SetOrKeep, Permission, Preconditions, @@ -52,34 +104,23 @@ export { ZkappCommand, addMissingSignatures, addMissingProofs, - ZkappStateLength, Events, Actions, TokenId, - Token, CallForest, - createChildAccountUpdate, - AccountUpdatesLayout, zkAppProver, - SmartContractContext, dummySignature, + LazyProof, + AccountUpdateTreeBase, + AccountUpdateLayout, + hashAccountUpdate, + HashedAccountUpdate, }; -const ZkappStateLength = 8; - const TransactionVersion = { current: () => UInt32.from(protocolVersions.txnVersion), }; -type SmartContractContext = { - this: SmartContract; - methodCallDepth: number; - selfUpdate: AccountUpdate; -}; -let smartContractContext = Context.create({ - default: null, -}); - type ZkappProverData = { transaction: ZkappCommand; accountUpdate: AccountUpdate; @@ -94,11 +135,6 @@ type Update = AccountUpdateBody['update']; type MayUseToken = AccountUpdateBody['mayUseToken']; -/** - * Preconditions for the network and accounts - */ -type Preconditions = AccountUpdateBody['preconditions']; - /** * Either set a value or keep it the same. */ @@ -412,7 +448,7 @@ interface Body extends AccountUpdateBody { * Events can be collected by archive nodes. * * [Check out our documentation about - * Events!](https://docs.minaprotocol.com/zkapps/advanced-snarkyjs/events) + * Events!](https://docs.minaprotocol.com/zkapps/advanced-o1js/events) */ events: Events; /** @@ -421,7 +457,7 @@ interface Body extends AccountUpdateBody { * a {@link Reducer}. * * [Check out our documentation about - * Actions!](https://docs.minaprotocol.com/zkapps/advanced-snarkyjs/actions-and-reducer) + * Actions!](https://docs.minaprotocol.com/zkapps/advanced-o1js/actions-and-reducer) */ actions: Events; /** @@ -468,7 +504,7 @@ const Body = { tokenId?: Field, mayUseToken?: MayUseToken ): Body { - let { body } = Types.AccountUpdate.emptyValue(); + let { body } = Types.AccountUpdate.empty(); body.publicKey = publicKey; if (tokenId) { body.tokenId = tokenId; @@ -486,7 +522,7 @@ const Body = { }, dummy(): Body { - return Types.AccountUpdate.emptyValue().body; + return Types.AccountUpdate.empty().body; }, }; @@ -506,115 +542,11 @@ type FeePayerUnsigned = FeePayer & { lazyAuthorization?: LazySignature | undefined; }; -/** - * Either check a value or ignore it. - * - * Used within [[ AccountPredicate ]]s and [[ ProtocolStatePredicate ]]s. - */ -type OrIgnore = { isSome: Bool; value: T }; - -/** - * An interval representing all the values between `lower` and `upper` inclusive - * of both the `lower` and `upper` values. - * - * @typeParam A something with an ordering where one can quantify a lower and - * upper bound. - */ -type ClosedInterval = { lower: T; upper: T }; - -type NetworkPrecondition = Preconditions['network']; -let NetworkPrecondition = { - ignoreAll(): NetworkPrecondition { - let stakingEpochData = { - ledger: { hash: ignore(Field(0)), totalCurrency: ignore(uint64()) }, - seed: ignore(Field(0)), - startCheckpoint: ignore(Field(0)), - lockCheckpoint: ignore(Field(0)), - epochLength: ignore(uint32()), - }; - let nextEpochData = cloneCircuitValue(stakingEpochData); - return { - snarkedLedgerHash: ignore(Field(0)), - blockchainLength: ignore(uint32()), - minWindowDensity: ignore(uint32()), - totalCurrency: ignore(uint64()), - globalSlotSinceGenesis: ignore(uint32()), - stakingEpochData, - nextEpochData, - }; - }, -}; - -/** - * Ignores a `dummy` - * - * @param dummy The value to ignore - * @returns Always an ignored value regardless of the input. - */ -function ignore(dummy: T): OrIgnore { - return { isSome: Bool(false), value: dummy }; -} - -/** - * Ranges between all uint32 values - */ -const uint32 = () => ({ lower: UInt32.from(0), upper: UInt32.MAXINT() }); - -/** - * Ranges between all uint64 values - */ -const uint64 = () => ({ lower: UInt64.from(0), upper: UInt64.MAXINT() }); - -type AccountPrecondition = Preconditions['account']; -const AccountPrecondition = { - ignoreAll(): AccountPrecondition { - let appState: Array> = []; - for (let i = 0; i < ZkappStateLength; ++i) { - appState.push(ignore(Field(0))); - } - return { - balance: ignore(uint64()), - nonce: ignore(uint32()), - receiptChainHash: ignore(Field(0)), - delegate: ignore(PublicKey.empty()), - state: appState, - actionState: ignore(Actions.emptyActionState()), - provedState: ignore(Bool(false)), - isNew: ignore(Bool(false)), - }; - }, - nonce(nonce: UInt32): AccountPrecondition { - let p = AccountPrecondition.ignoreAll(); - AccountUpdate.assertEquals(p.nonce, nonce); - return p; - }, -}; - -type GlobalSlotPrecondition = Preconditions['validWhile']; -const GlobalSlotPrecondition = { - ignoreAll(): GlobalSlotPrecondition { - return ignore(uint32()); - }, -}; - -const Preconditions = { - ignoreAll(): Preconditions { - return { - account: AccountPrecondition.ignoreAll(), - network: NetworkPrecondition.ignoreAll(), - validWhile: GlobalSlotPrecondition.ignoreAll(), - }; - }, -}; - type Control = Types.AccountUpdate['authorization']; type LazyNone = { kind: 'lazy-none'; }; -type LazySignature = { - kind: 'lazy-signature'; - privateKey?: PrivateKey; -}; +type LazySignature = { kind: 'lazy-signature' }; type LazyProof = { kind: 'lazy-proof'; methodName: string; @@ -625,10 +557,7 @@ type LazyProof = { blindingValue: Field; }; -const AccountId = provable( - { tokenOwner: PublicKey, parentTokenId: Field }, - { customObjectKeys: ['tokenOwner', 'parentTokenId'] } -); +const AccountId = provable({ tokenOwner: PublicKey, parentTokenId: Field }); const TokenId = { ...Types.TokenId, @@ -642,38 +571,6 @@ const TokenId = { }, }; -/** - * @deprecated use `TokenId` instead of `Token.Id` and `TokenId.derive()` instead of `Token.getId()` - */ -class Token { - static Id = TokenId; - - static getId(tokenOwner: PublicKey, parentTokenId = TokenId.default) { - return TokenId.derive(tokenOwner, parentTokenId); - } - - readonly id: Field; - readonly parentTokenId: Field; - readonly tokenOwner: PublicKey; - constructor({ - tokenOwner, - parentTokenId = TokenId.default, - }: { - tokenOwner: PublicKey; - parentTokenId?: Field; - }) { - this.parentTokenId = parentTokenId; - this.tokenOwner = tokenOwner; - try { - this.id = TokenId.derive(tokenOwner, parentTokenId); - } catch (e) { - throw new Error( - `Could not create a custom token id:\nError: ${(e as Error).message}` - ); - } - } -} - /** * An {@link AccountUpdate} is a set of instructions for the Mina network. * It includes {@link Preconditions} and a list of state updates, which need to @@ -690,20 +587,9 @@ class AccountUpdate implements Types.AccountUpdate { authorization: Control; lazyAuthorization: LazySignature | LazyProof | LazyNone | undefined = undefined; - account: Precondition.Account; - network: Precondition.Network; - currentSlot: Precondition.CurrentSlot; - children: { - callsType: - | { type: 'None' } - | { type: 'Witness' } - | { type: 'Equals'; value: Field }; - accountUpdates: AccountUpdate[]; - } = { - callsType: { type: 'None' }, - accountUpdates: [], - }; - parent: AccountUpdate | undefined = undefined; + account: Account; + network: Network; + currentSlot: CurrentSlot; private isSelf: boolean; @@ -714,11 +600,7 @@ class AccountUpdate implements Types.AccountUpdate { this.id = Math.random(); this.body = body; this.authorization = authorization; - let { account, network, currentSlot } = Precondition.preconditions( - this, - isSelf - ); - + let { account, network, currentSlot } = preconditions(this, isSelf); this.account = account; this.network = network; this.currentSlot = currentSlot; @@ -737,127 +619,15 @@ class AccountUpdate implements Types.AccountUpdate { accountUpdate.isSelf ); cloned.lazyAuthorization = accountUpdate.lazyAuthorization; - cloned.children.callsType = accountUpdate.children.callsType; - cloned.children.accountUpdates = accountUpdate.children.accountUpdates.map( - AccountUpdate.clone - ); cloned.id = accountUpdate.id; cloned.label = accountUpdate.label; - cloned.parent = accountUpdate.parent; return cloned; } - token() { - let thisAccountUpdate = this; - let tokenOwner = this.publicKey; - let parentTokenId = this.tokenId; - let id = TokenId.derive(tokenOwner, parentTokenId); - - function getApprovedAccountUpdate( - accountLike: PublicKey | AccountUpdate | SmartContract, - label: string - ) { - if (accountLike instanceof SmartContract) { - accountLike = accountLike.self; - } - if (accountLike instanceof AccountUpdate) { - accountLike.tokenId.assertEquals(id); - thisAccountUpdate.approve(accountLike); - } - if (accountLike instanceof PublicKey) { - accountLike = AccountUpdate.defaultAccountUpdate(accountLike, id); - makeChildAccountUpdate(thisAccountUpdate, accountLike); - } - if (!accountLike.label) - accountLike.label = `${ - thisAccountUpdate.label ?? 'Unlabeled' - }.${label}`; - return accountLike; - } - - return { - id, - parentTokenId, - tokenOwner, - - /** - * Mints token balance to `address`. Returns the mint account update. - */ - mint({ - address, - amount, - }: { - address: PublicKey | AccountUpdate | SmartContract; - amount: number | bigint | UInt64; - }) { - let receiver = getApprovedAccountUpdate(address, 'token.mint()'); - receiver.balance.addInPlace(amount); - return receiver; - }, - - /** - * Burn token balance on `address`. Returns the burn account update. - */ - burn({ - address, - amount, - }: { - address: PublicKey | AccountUpdate | SmartContract; - amount: number | bigint | UInt64; - }) { - let sender = getApprovedAccountUpdate(address, 'token.burn()'); - - // Sub the amount to burn from the sender's account - sender.balance.subInPlace(amount); - - // Require signature from the sender account being deducted - sender.body.useFullCommitment = Bool(true); - Authorization.setLazySignature(sender); - return sender; - }, - - /** - * Move token balance from `from` to `to`. Returns the `to` account update. - */ - send({ - from, - to, - amount, - }: { - from: PublicKey | AccountUpdate | SmartContract; - to: PublicKey | AccountUpdate | SmartContract; - amount: number | bigint | UInt64; - }) { - let sender = getApprovedAccountUpdate(from, 'token.send() (sender)'); - sender.balance.subInPlace(amount); - sender.body.useFullCommitment = Bool(true); - Authorization.setLazySignature(sender); - - let receiver = getApprovedAccountUpdate(to, 'token.send() (receiver)'); - receiver.balance.addInPlace(amount); - - return receiver; - }, - }; - } - get tokenId() { return this.body.tokenId; } - /** - * @deprecated use `this.account.tokenSymbol` - */ - get tokenSymbol() { - let accountUpdate = this; - - return { - set(tokenSymbol: string) { - accountUpdate.account.tokenSymbol.set(tokenSymbol); - }, - }; - } - send({ to, amount, @@ -869,7 +639,7 @@ class AccountUpdate implements Types.AccountUpdate { if (to instanceof AccountUpdate) { receiver = to; receiver.body.tokenId.assertEquals(this.body.tokenId); - } else if (to instanceof SmartContract) { + } else if (isSmartContract(to)) { receiver = to.self; receiver.body.tokenId.assertEquals(this.body.tokenId); } else { @@ -890,15 +660,25 @@ class AccountUpdate implements Types.AccountUpdate { } /** - * Makes an {@link AccountUpdate} a child-{@link AccountUpdate} of this and - * approves it. + * Makes another {@link AccountUpdate} a child of this one. + * + * The parent-child relationship means that the child becomes part of the "statement" + * of the parent, and goes into the commitment that is authorized by either a signature + * or a proof. + * + * For a proof in particular, child account updates are contained in the public input + * of the proof that authorizes the parent account update. */ - approve( - childUpdate: AccountUpdate, - layout: AccountUpdatesLayout = AccountUpdate.Layout.NoChildren - ) { - makeChildAccountUpdate(this, childUpdate); - AccountUpdate.witnessChildren(childUpdate, layout, { skipCheck: true }); + approve(child: AccountUpdate | AccountUpdateTree | AccountUpdateForest) { + if (child instanceof AccountUpdateForest) { + accountUpdateLayout()?.setChildren(this, child); + return; + } + if (child instanceof AccountUpdate) { + child.body.callDepth = this.body.callDepth + 1; + } + accountUpdateLayout()?.disattach(child); + accountUpdateLayout()?.pushChild(this, child); } get balance() { @@ -916,6 +696,13 @@ class AccountUpdate implements Types.AccountUpdate { }; } + get balanceChange() { + return Int64.fromObject(this.body.balanceChange); + } + set balanceChange(x: Int64) { + this.body.balanceChange = x; + } + get update(): Update { return this.body.update; } @@ -996,7 +783,7 @@ class AccountUpdate implements Types.AccountUpdate { * then you should use the following code before sending your transaction: * * ```ts - * let tx = Mina.transaction(...); // create transaction as usual, using `requireSignature()` somewhere + * let tx = await Mina.transaction(...); // create transaction as usual, using `requireSignature()` somewhere * tx.sign([privateKey]); // pass the private key of this account to `sign()`! * ``` * @@ -1004,12 +791,6 @@ class AccountUpdate implements Types.AccountUpdate { * be (can be) authorized by a signature. */ requireSignature() { - this.sign(); - } - /** - * @deprecated `.sign()` is deprecated in favor of `.requireSignature()` - */ - sign(privateKey?: PrivateKey) { let { nonce, isSameAsFeePayer } = AccountUpdate.getSigningInfo(this); // if this account is the same as the fee payer, we use the "full commitment" for replay protection this.body.useFullCommitment = isSameAsFeePayer; @@ -1024,16 +805,13 @@ class AccountUpdate implements Types.AccountUpdate { this.body.preconditions.account.nonce.value.lower = lower; this.body.preconditions.account.nonce.value.upper = upper; // set lazy signature - Authorization.setLazySignature(this, { privateKey }); + Authorization.setLazySignature(this); } - static signFeePayerInPlace( - feePayer: FeePayerUnsigned, - privateKey?: PrivateKey - ) { + static signFeePayerInPlace(feePayer: FeePayerUnsigned) { feePayer.body.nonce = this.getNonce(feePayer); feePayer.authorization = dummySignature(); - feePayer.lazyAuthorization = { kind: 'lazy-signature', privateKey }; + feePayer.lazyAuthorization = { kind: 'lazy-signature' }; } static getNonce(accountUpdate: AccountUpdate | FeePayerUnsigned) { @@ -1041,8 +819,8 @@ class AccountUpdate implements Types.AccountUpdate { } private static signingInfo = provable({ - nonce: UInt32, isSameAsFeePayer: Bool, + nonce: UInt32, }); private static getSigningInfo( @@ -1059,30 +837,25 @@ class AccountUpdate implements Types.AccountUpdate { let publicKey = update.body.publicKey; let tokenId = update instanceof AccountUpdate ? update.body.tokenId : TokenId.default; - let nonce = Number( - Precondition.getAccountPreconditions(update.body).nonce.toString() - ); + let nonce = Number(getAccountPreconditions(update.body).nonce.toString()); // if the fee payer is the same account update as this one, we have to start // the nonce predicate at one higher, bc the fee payer already increases its // nonce - let isFeePayer = Mina.currentTransaction()?.sender?.equals(publicKey); + let isFeePayer = currentTransaction()?.sender?.equals(publicKey); let isSameAsFeePayer = !!isFeePayer ?.and(tokenId.equals(TokenId.default)) .toBoolean(); if (isSameAsFeePayer) nonce++; // now, we check how often this account update already updated its nonce in // this tx, and increase nonce from `getAccount` by that amount - CallForest.forEachPredecessor( - Mina.currentTransaction.get().accountUpdates, - update as AccountUpdate, - (otherUpdate) => { - let shouldIncreaseNonce = otherUpdate.publicKey - .equals(publicKey) - .and(otherUpdate.tokenId.equals(tokenId)) - .and(otherUpdate.body.incrementNonce); - if (shouldIncreaseNonce.toBoolean()) nonce++; - } - ); + let layout = currentTransaction()?.layout; + layout?.forEachPredecessor(update as AccountUpdate, (otherUpdate) => { + let shouldIncreaseNonce = otherUpdate.publicKey + .equals(publicKey) + .and(otherUpdate.tokenId.equals(tokenId)) + .and(otherUpdate.body.incrementNonce); + if (shouldIncreaseNonce.toBoolean()) nonce++; + }); return { nonce: UInt32.from(nonce), isSameAsFeePayer: Bool(isSameAsFeePayer), @@ -1110,24 +883,74 @@ class AccountUpdate implements Types.AccountUpdate { // implementations are equivalent, and catch regressions quickly if (Provable.inCheckedComputation()) { let input = Types.AccountUpdate.toInput(this); - return hashWithPrefix(prefixes.body, packToFields(input)); + return hashWithPrefix( + zkAppBodyPrefix(activeInstance.getNetworkId()), + packToFields(input) + ); } else { let json = Types.AccountUpdate.toJSON(this); - return Field(Test.hashFromJson.accountUpdate(JSON.stringify(json))); + return Field( + Test.hashFromJson.accountUpdate( + JSON.stringify(json), + NetworkId.toString(activeInstance.getNetworkId()) + ) + ); } } - toPublicInput(): ZkappPublicInput { + toPublicInput({ + accountUpdates, + }: { + accountUpdates: AccountUpdate[]; + }): ZkappPublicInput { let accountUpdate = this.hash(); - let calls = CallForest.hashChildren(this); + + // collect this update's descendants + let descendants: AccountUpdate[] = []; + let callDepth = this.body.callDepth; + let i = accountUpdates.findIndex((a) => a.id === this.id); + assert(i !== -1, 'Account update not found in transaction'); + for (i++; i < accountUpdates.length; i++) { + let update = accountUpdates[i]; + if (update.body.callDepth <= callDepth) break; + descendants.push(update); + } + + // call forest hash + let forest = accountUpdatesToCallForest(descendants, callDepth + 1); + let calls = callForestHashGeneric( + forest, + (a) => a.hash(), + Poseidon.hashWithPrefix, + emptyHash, + activeInstance.getNetworkId() + ); return { accountUpdate, calls }; } + toPrettyLayout() { + let node = accountUpdateLayout()?.get(this); + assert(node !== undefined, 'AccountUpdate not found in layout'); + node.children.print(); + } + + extractTree(): AccountUpdateTree { + let layout = accountUpdateLayout(); + let hash = layout?.get(this)?.final?.hash; + let id = this.id; + let children = + layout?.finalizeAndRemove(this) ?? AccountUpdateForest.empty(); + let accountUpdate = HashedAccountUpdate.hash(this, hash); + return new AccountUpdateTree({ accountUpdate, id, children }); + } + static defaultAccountUpdate(address: PublicKey, tokenId?: Field) { return new AccountUpdate(Body.keepAll(address, tokenId)); } static dummy() { - return new AccountUpdate(Body.dummy()); + let dummy = new AccountUpdate(Body.dummy()); + dummy.label = 'Dummy'; + return dummy; } isDummy() { return this.body.publicKey.isEmpty(); @@ -1164,8 +987,8 @@ class AccountUpdate implements Types.AccountUpdate { self.label || 'Unlabeled' } > AccountUpdate.create()`; } else { - Mina.currentTransaction()?.accountUpdates.push(accountUpdate); - accountUpdate.label = `Mina.transaction > AccountUpdate.create()`; + currentTransaction()?.layout.pushTopLevel(accountUpdate); + accountUpdate.label = `Mina.transaction() > AccountUpdate.create()`; } return accountUpdate; } @@ -1183,26 +1006,15 @@ class AccountUpdate implements Types.AccountUpdate { if (selfUpdate === accountUpdate) return; insideContract.this.self.approve(accountUpdate); } else { - if (!Mina.currentTransaction.has()) return; - let updates = Mina.currentTransaction.get().accountUpdates; - if (!updates.find((update) => update.id === accountUpdate.id)) { - updates.push(accountUpdate); - } + if (!currentTransaction.has()) return; + currentTransaction.get().layout.pushTopLevel(accountUpdate); } } /** * Disattach an account update from where it's currently located in the transaction */ static unlink(accountUpdate: AccountUpdate) { - let siblings = - accountUpdate.parent?.children.accountUpdates ?? - Mina.currentTransaction()?.accountUpdates; - if (siblings === undefined) return; - let i = siblings?.findIndex((update) => update.id === accountUpdate.id); - if (i !== undefined && i !== -1) { - siblings!.splice(i, 1); - } - accountUpdate.parent === undefined; + accountUpdateLayout()?.disattach(accountUpdate); } /** @@ -1213,31 +1025,20 @@ class AccountUpdate implements Types.AccountUpdate { * then you should use the following code before sending your transaction: * * ```ts - * let tx = Mina.transaction(...); // create transaction as usual, using `createSigned()` somewhere + * let tx = await Mina.transaction(...); // create transaction as usual, using `createSigned()` somewhere * tx.sign([privateKey]); // pass the private key of this account to `sign()`! * ``` * * Note that an account's {@link Permissions} determine which updates have to * be (can be) authorized by a signature. */ - static createSigned(signer: PublicKey, tokenId?: Field): AccountUpdate; - /** - * @deprecated in favor of calling this function with a `PublicKey` as `signer` - */ - static createSigned(signer: PrivateKey, tokenId?: Field): AccountUpdate; - static createSigned(signer: PrivateKey | PublicKey, tokenId?: Field) { - let publicKey = - signer instanceof PrivateKey ? signer.toPublicKey() : signer; + static createSigned(publicKey: PublicKey, tokenId?: Field) { let accountUpdate = AccountUpdate.create(publicKey, tokenId); accountUpdate.label = accountUpdate.label.replace( '.create()', '.createSigned()' ); - if (signer instanceof PrivateKey) { - accountUpdate.sign(signer); - } else { - accountUpdate.requireSignature(); - } + accountUpdate.requireSignature(); return accountUpdate; } @@ -1252,32 +1053,11 @@ class AccountUpdate implements Types.AccountUpdate { * @param numberOfAccounts the number of new accounts to fund (default: 1) * @returns they {@link AccountUpdate} for the account which pays the fee */ - static fundNewAccount( - feePayer: PublicKey, - numberOfAccounts?: number - ): AccountUpdate; - /** - * @deprecated Call this function with a `PublicKey` as `feePayer`, and remove the `initialBalance` option. - * To send an initial balance to the new account, you can use the returned account update: - * ``` - * let feePayerUpdate = AccountUpdate.fundNewAccount(feePayer); - * feePayerUpdate.send({ to: receiverAddress, amount: initialBalance }); - * ``` - */ - static fundNewAccount( - feePayer: PrivateKey | PublicKey, - options?: { initialBalance: number | string | UInt64 } | number - ): AccountUpdate; - static fundNewAccount( - feePayer: PrivateKey | PublicKey, - numberOfAccounts?: number | { initialBalance: number | string | UInt64 } - ) { - let accountUpdate = AccountUpdate.createSigned(feePayer as PrivateKey); + static fundNewAccount(feePayer: PublicKey, numberOfAccounts = 1) { + let accountUpdate = AccountUpdate.createSigned(feePayer); accountUpdate.label = 'AccountUpdate.fundNewAccount()'; - let fee = Mina.accountCreationFee(); - numberOfAccounts ??= 1; - if (typeof numberOfAccounts === 'number') fee = fee.mul(numberOfAccounts); - else fee = fee.add(UInt64.from(numberOfAccounts.initialBalance ?? 0)); + let fee = activeInstance.getNetworkConstants().accountCreationFee; + fee = fee.mul(numberOfAccounts); accountUpdate.balance.subInPlace(fee); return accountUpdate; } @@ -1287,23 +1067,15 @@ class AccountUpdate implements Types.AccountUpdate { static toFields = Types.AccountUpdate.toFields; static toAuxiliary(a?: AccountUpdate) { let aux = Types.AccountUpdate.toAuxiliary(a); - let children: AccountUpdate['children'] = { - callsType: { type: 'None' }, - accountUpdates: [], - }; let lazyAuthorization = a && a.lazyAuthorization; - if (a) { - children.callsType = a.children.callsType; - children.accountUpdates = a.children.accountUpdates.map( - AccountUpdate.clone - ); - } - let parent = a?.parent; let id = a?.id ?? Math.random(); let label = a?.label ?? ''; - return [{ lazyAuthorization, children, parent, id, label }, aux]; + return [{ lazyAuthorization, id, label }, aux]; } static toInput = Types.AccountUpdate.toInput; + static empty() { + return AccountUpdate.dummy(); + } static check = Types.AccountUpdate.check; static fromFields(fields: Field[], [other, aux]: any[]): AccountUpdate { let accountUpdate = Types.AccountUpdate.fromFields(fields, aux); @@ -1315,7 +1087,7 @@ class AccountUpdate implements Types.AccountUpdate { static witness( type: FlexibleProvable, - compute: () => { accountUpdate: AccountUpdate; result: T }, + compute: () => Promise<{ accountUpdate: AccountUpdate; result: T }>, { skipCheck = false } = {} ) { // construct the circuit type for a accountUpdate + other result @@ -1326,119 +1098,12 @@ class AccountUpdate implements Types.AccountUpdate { accountUpdate: accountUpdateType, result: type as any, }); - return Provable.witness(combinedType, compute); - } - - static witnessChildren( - accountUpdate: AccountUpdate, - childLayout: AccountUpdatesLayout, - options?: { skipCheck: boolean } - ) { - // just witness children's hash if childLayout === null - if (childLayout === AccountUpdate.Layout.AnyChildren) { - accountUpdate.children.callsType = { type: 'Witness' }; - return; - } - if (childLayout === AccountUpdate.Layout.NoDelegation) { - accountUpdate.children.callsType = { type: 'Witness' }; - accountUpdate.body.mayUseToken.parentsOwnToken.assertFalse(); - accountUpdate.body.mayUseToken.inheritFromParent.assertFalse(); - return; - } - let childArray: AccountUpdatesLayout[] = - typeof childLayout === 'number' - ? Array(childLayout).fill(AccountUpdate.Layout.NoChildren) - : childLayout; - let n = childArray.length; - for (let i = 0; i < n; i++) { - accountUpdate.children.accountUpdates[i] = AccountUpdate.witnessTree( - provable(null), - childArray[i], - () => ({ - accountUpdate: - accountUpdate.children.accountUpdates[i] ?? AccountUpdate.dummy(), - result: null, - }), - options - ).accountUpdate; - } - if (n === 0) { - accountUpdate.children.callsType = { - type: 'Equals', - value: CallForest.emptyHash(), - }; - } - } - - /** - * Like AccountUpdate.witness, but lets you specify a layout for the - * accountUpdate's children, which also get witnessed - */ - static witnessTree( - resultType: FlexibleProvable, - childLayout: AccountUpdatesLayout, - compute: () => { - accountUpdate: AccountUpdate; - result: T; - }, - options?: { skipCheck: boolean } - ) { - // witness the root accountUpdate - let { accountUpdate, result } = AccountUpdate.witness( - resultType, - compute, - options - ); - // witness child account updates - AccountUpdate.witnessChildren(accountUpdate, childLayout, options); - return { accountUpdate, result }; + return Provable.witnessAsync(combinedType, compute); } - /** - * Describes the children of an account update, which are laid out in a tree. - * - * The tree layout is described recursively by using a combination of `AccountUpdate.Layout.NoChildren`, `AccountUpdate.Layout.StaticChildren(...)` and `AccountUpdate.Layout.AnyChildren`. - * - `NoChildren` means an account update that can't have children - * - `AnyChildren` means an account update can have an arbitrary amount of children, which means you can't access those children in your circuit (because the circuit is static). - * - `StaticChildren` means the account update must have a certain static amount of children and expects as arguments a description of each of those children. - * As a shortcut, you can also pass `StaticChildren` a number, which means it has that amount of children but no grandchildren. - * - * This is best understood by examples: - * - * ```ts - * let { NoChildren, AnyChildren, StaticChildren } = AccounUpdate.Layout; - * - * NoChildren // an account update with no children - * AnyChildren // an account update with arbitrary children - * StaticChildren(NoChildren) // an account update with 1 child, which doesn't have children itself - * StaticChildren(1) // shortcut for StaticChildren(NoChildren) - * StaticChildren(2) // shortcut for StaticChildren(NoChildren, NoChildren) - * StaticChildren(0) // equivalent to NoChildren - * - * // an update with 2 children, of which one has arbitrary children and the other has exactly 1 descendant - * StaticChildren(AnyChildren, StaticChildren(1)) - * ``` - */ - static Layout = { - StaticChildren: ((...args: any[]) => { - if (args.length === 1 && typeof args[0] === 'number') return args[0]; - if (args.length === 0) return 0; - return args; - }) as { - (n: number): AccountUpdatesLayout; - (...args: AccountUpdatesLayout[]): AccountUpdatesLayout; - }, - NoChildren: 0, - AnyChildren: 'AnyChildren' as const, - NoDelegation: 'NoDelegation' as const, - }; - static get MayUseToken() { return { - type: provablePure( - { parentsOwnToken: Bool, inheritFromParent: Bool }, - { customObjectKeys: ['parentsOwnToken', 'inheritFromParent'] } - ), + type: provablePure({ parentsOwnToken: Bool, inheritFromParent: Bool }), No: { parentsOwnToken: Bool(false), inheritFromParent: Bool(false) }, ParentsOwnToken: { parentsOwnToken: Bool(true), @@ -1532,6 +1197,15 @@ class AccountUpdate implements Types.AccountUpdate { body[key] = JSON.stringify(body[key]) as any; } } + if (body.authorizationKind?.isProved === false) { + delete (body as any).authorizationKind?.verificationKeyHash; + } + if ( + body.authorizationKind?.isProved === false && + body.authorizationKind?.isSigned === false + ) { + delete (body as any).authorizationKind; + } if ( jsonUpdate.authorization !== undefined || body.authorizationKind?.isProved === true || @@ -1539,6 +1213,7 @@ class AccountUpdate implements Types.AccountUpdate { ) { (body as any).authorization = jsonUpdate.authorization; } + body.mayUseToken = { parentsOwnToken: this.body.mayUseToken.parentsOwnToken.toBoolean(), inheritFromParent: this.body.mayUseToken.inheritFromParent.toBoolean(), @@ -1551,206 +1226,512 @@ class AccountUpdate implements Types.AccountUpdate { } } -type AccountUpdatesLayout = - | number - | 'AnyChildren' - | 'NoDelegation' - | AccountUpdatesLayout[]; +// call forest stuff -type WithCallers = { - accountUpdate: AccountUpdate; - caller: Field; - children: WithCallers[]; +function hashAccountUpdate(update: AccountUpdate) { + return genericHash( + AccountUpdate, + zkAppBodyPrefix(activeInstance.getNetworkId()), + update + ); +} + +class HashedAccountUpdate extends Hashed.create( + AccountUpdate, + hashAccountUpdate +) {} + +type AccountUpdateTreeBase = { + id: number; + accountUpdate: Hashed; + children: AccountUpdateForestBase; }; +type AccountUpdateForestBase = MerkleListBase; + +const AccountUpdateTreeBase = StructNoJson({ + id: RandomId, + accountUpdate: HashedAccountUpdate.provable, + children: MerkleListBase(), +}); -const CallForest = { - // similar to Mina_base.ZkappCommand.Call_forest.to_account_updates_list - // takes a list of accountUpdates, which each can have children, so they form a "forest" (list of trees) - // returns a flattened list, with `accountUpdate.body.callDepth` specifying positions in the forest - // also removes any "dummy" accountUpdates - toFlatList( - forest: AccountUpdate[], +/** + * Class which represents a forest (list of trees) of account updates, + * in a compressed way which allows iterating and selectively witnessing the account updates. + * + * The (recursive) type signature is: + * ``` + * type AccountUpdateForest = MerkleList; + * type AccountUpdateTree = { + * accountUpdate: Hashed; + * children: AccountUpdateForest; + * }; + * ``` + */ +class AccountUpdateForest extends MerkleList.create( + AccountUpdateTreeBase, + merkleListHash +) { + static fromFlatArray(updates: AccountUpdate[]): AccountUpdateForest { + let simpleForest = accountUpdatesToCallForest(updates); + return this.fromSimpleForest(simpleForest); + } + static toFlatArray( + forest: AccountUpdateForestBase, mutate = true, depth = 0 - ): AccountUpdate[] { - let accountUpdates = []; - for (let accountUpdate of forest) { - if (accountUpdate.isDummy().toBoolean()) continue; - if (mutate) accountUpdate.body.callDepth = depth; - let children = accountUpdate.children.accountUpdates; - accountUpdates.push( - accountUpdate, - ...CallForest.toFlatList(children, mutate, depth + 1) - ); + ) { + let flat: AccountUpdate[] = []; + for (let { element: tree } of forest.data.get()) { + let update = tree.accountUpdate.value.get(); + if (mutate) update.body.callDepth = depth; + flat.push(update); + flat.push(...this.toFlatArray(tree.children, mutate, depth + 1)); } - return accountUpdates; - }, + return flat; + } - // Mina_base.Zkapp_command.Digest.Forest.empty - emptyHash() { - return Field(0); - }, + private static fromSimpleForest( + simpleForest: CallForest + ): AccountUpdateForest { + let nodes = simpleForest.map((node) => { + let accountUpdate = HashedAccountUpdate.hash(node.accountUpdate); + let children = AccountUpdateForest.fromSimpleForest(node.children); + return { accountUpdate, children, id: node.accountUpdate.id }; + }); + return AccountUpdateForest.from(nodes); + } + + // TODO this comes from paranoia and might be removed later + static assertConstant(forest: AccountUpdateForestBase) { + Provable.asProver(() => { + forest.data.get().forEach(({ element: tree }) => { + assert( + Provable.isConstant(AccountUpdate, tree.accountUpdate.value.get()), + 'account update not constant' + ); + AccountUpdateForest.assertConstant(tree.children); + }); + }); + } +} - // similar to Mina_base.Zkapp_command.Call_forest.accumulate_hashes - // hashes a accountUpdate's children (and their children, and ...) to compute - // the `calls` field of ZkappPublicInput - hashChildren(update: AccountUpdate): Field { - let { callsType } = update.children; - // compute hash outside the circuit if callsType is "Witness" - // i.e., allowing accountUpdates with arbitrary children - if (callsType.type === 'Witness') { - return Provable.witness(Field, () => CallForest.hashChildrenBase(update)); +/** + * Class which represents a tree of account updates, + * in a compressed way which allows iterating and selectively witnessing the account updates. + * + * The (recursive) type signature is: + * ``` + * type AccountUpdateTree = { + * accountUpdate: Hashed; + * children: AccountUpdateForest; + * }; + * type AccountUpdateForest = MerkleList; + * ``` + */ +class AccountUpdateTree extends StructNoJson({ + id: RandomId, + accountUpdate: HashedAccountUpdate.provable, + children: AccountUpdateForest.provable, +}) { + /** + * Create a tree of account updates which only consists of a root. + */ + static from(update: AccountUpdate | AccountUpdateTree, hash?: Field) { + if (update instanceof AccountUpdateTree) return update; + return new AccountUpdateTree({ + accountUpdate: HashedAccountUpdate.hash(update, hash), + id: update.id, + children: AccountUpdateForest.empty(), + }); + } + + /** + * Add an {@link AccountUpdate} or {@link AccountUpdateTree} to the children of this tree's root. + * + * See {@link AccountUpdate.approve}. + */ + approve(update: AccountUpdate | AccountUpdateTree, hash?: Field) { + accountUpdateLayout()?.disattach(update); + if (update instanceof AccountUpdate) { + this.children.pushIf( + update.isDummy().not(), + AccountUpdateTree.from(update, hash) + ); + } else { + this.children.push(update); } - let calls = CallForest.hashChildrenBase(update); - if (callsType.type === 'Equals' && Provable.inCheckedComputation()) { - calls.assertEquals(callsType.value); + } + + // fix Struct type + static fromFields(fields: Field[], aux: any) { + return new AccountUpdateTree(super.fromFields(fields, aux)); + } + static empty() { + return new AccountUpdateTree(super.empty()); + } +} + +// how to hash a forest + +function merkleListHash(forestHash: Field, tree: AccountUpdateTreeBase) { + return hashCons(forestHash, hashNode(tree)); +} + +function hashNode(tree: AccountUpdateTreeBase) { + return Poseidon.hashWithPrefix(prefixes.accountUpdateNode, [ + tree.accountUpdate.hash, + tree.children.hash, + ]); +} +function hashCons(forestHash: Field, nodeHash: Field) { + return Poseidon.hashWithPrefix(prefixes.accountUpdateCons, [ + nodeHash, + forestHash, + ]); +} + +/** + * `UnfinishedForest` / `UnfinishedTree` are structures for constructing the forest of child account updates from a circuit. + * + * The circuit can mutate account updates and change their array of children, so here we can't hash + * everything immediately. Instead, we maintain a structure consisting of either hashes or full account + * updates that can be hashed into a final call forest at the end. + * + * `UnfinishedForest` and `UnfinishedTree` behave like a tagged enum type: + * ``` + * type UnfinishedForest = + * | Mutable of UnfinishedTree[] + * | Final of AccountUpdateForest; + * + * type UnfinishedTree = ( + * | Mutable of AccountUpdate + * | Final of HashedAccountUpdate + * ) & { children: UnfinishedForest, ... } + * ``` + */ +type UnfinishedTree = { + id: number; + isDummy: Bool; + // `children` must be readonly since it's referenced in each child's siblings + readonly children: UnfinishedForest; + siblings?: UnfinishedForest; +} & ( + | { final: HashedAccountUpdate; mutable?: undefined } + | { final?: undefined; mutable: AccountUpdate } +); + +type UnfinishedForestFinal = UnfinishedForest & { + final: AccountUpdateForest; + mutable?: undefined; +}; + +type UnfinishedForestMutable = UnfinishedForest & { + final?: undefined; + mutable: UnfinishedTree[]; +}; + +class UnfinishedForest { + final?: AccountUpdateForest; + mutable?: UnfinishedTree[]; + + isFinal(): this is UnfinishedForestFinal { + return this.final !== undefined; + } + isMutable(): this is UnfinishedForestMutable { + return this.mutable !== undefined; + } + + constructor(mutable?: UnfinishedTree[], final?: AccountUpdateForest) { + assert( + (final === undefined) !== (mutable === undefined), + 'final or mutable' + ); + this.final = final; + this.mutable = mutable; + } + + static empty(): UnfinishedForestMutable { + return new UnfinishedForest([]) as any; + } + + private setFinal(final: AccountUpdateForest): UnfinishedForestFinal { + return Object.assign(this, { final, mutable: undefined }); + } + + finalize(): AccountUpdateForest { + if (this.isFinal()) return this.final; + assert(this.isMutable(), 'final or mutable'); + + let nodes = this.mutable.map(UnfinishedTree.finalize); + let finalForest = AccountUpdateForest.empty(); + + for (let { isDummy, ...tree } of [...nodes].reverse()) { + finalForest.pushIf(isDummy.not(), tree); } - return calls; - }, + this.setFinal(finalForest); + return finalForest; + } + + witnessHash(): UnfinishedForestFinal { + let final = Provable.witness(AccountUpdateForest.provable, () => + this.finalize() + ); + return this.setFinal(final); + } + + push(node: UnfinishedTree) { + if (node.siblings === this) return; + assert( + node.siblings === undefined, + 'Cannot push node that already has a parent.' + ); + node.siblings = this; + assert(this.isMutable(), 'Cannot push to an immutable forest'); + this.mutable.push(node); + } - hashChildrenBase({ children }: AccountUpdate) { - let stackHash = CallForest.emptyHash(); - for (let accountUpdate of [...children.accountUpdates].reverse()) { - let calls = CallForest.hashChildren(accountUpdate); - let nodeHash = hashWithPrefix(prefixes.accountUpdateNode, [ - accountUpdate.hash(), - calls, - ]); - let newHash = hashWithPrefix(prefixes.accountUpdateCons, [ - nodeHash, - stackHash, - ]); - // skip accountUpdate if it's a dummy - stackHash = Provable.if(accountUpdate.isDummy(), stackHash, newHash); + remove(node: UnfinishedTree) { + assert(this.isMutable(), 'Cannot remove from an immutable forest'); + // find by .id + let index = this.mutable.findIndex((n) => n.id === node.id); + + // nothing to do if it's not there + if (index === -1) return; + + // remove it + node.siblings = undefined; + this.mutable.splice(index, 1); + } + + setToForest(forest: AccountUpdateForestBase) { + if (this.isMutable()) { + assert( + this.mutable.length === 0, + 'Replacing a mutable forest that has existing children might be a mistake.' + ); } - return stackHash; - }, + return this.setFinal(new AccountUpdateForest(forest)); + } - // Mina_base.Zkapp_command.Call_forest.add_callers - // TODO: currently unused, but could come back when we add caller to the - // public input - addCallers( - updates: AccountUpdate[], - context: { self: Field; caller: Field } = { - self: TokenId.default, - caller: TokenId.default, + static fromForest(forest: AccountUpdateForestBase) { + return UnfinishedForest.empty().setToForest(forest); + } + + toFlatArray(mutate = true, depth = 0): AccountUpdate[] { + if (this.isFinal()) + return AccountUpdateForest.toFlatArray(this.final, mutate, depth); + assert(this.isMutable(), 'final or mutable'); + let flatUpdates: AccountUpdate[] = []; + for (let node of this.mutable) { + if (node.isDummy.toBoolean()) continue; + let update = node.mutable ?? node.final.value.get(); + if (mutate) update.body.callDepth = depth; + let children = node.children.toFlatArray(mutate, depth + 1); + flatUpdates.push(update, ...children); } - ): WithCallers[] { - let withCallers: WithCallers[] = []; - for (let update of updates) { - let { mayUseToken } = update.body; - let caller = Provable.if( - mayUseToken.parentsOwnToken, - context.self, - Provable.if( - mayUseToken.inheritFromParent, - context.caller, - TokenId.default - ) - ); - let self = TokenId.derive(update.body.publicKey, update.body.tokenId); - let childContext = { caller, self }; - withCallers.push({ - accountUpdate: update, - caller, - children: CallForest.addCallers( - update.children.accountUpdates, - childContext - ), - }); + return flatUpdates; + } + + toConstantInPlace() { + if (this.isFinal()) { + this.final.hash = this.final.hash.toConstant(); + return; } - return withCallers; - }, - /** - * Used in the prover to witness the context from which to compute its caller - * - * TODO: currently unused, but could come back when we add caller to the - * public input - */ - computeCallerContext(update: AccountUpdate) { - // compute the line of ancestors - let current = update; - let ancestors = []; - while (true) { - let parent = current.parent; - if (parent === undefined) break; - ancestors.unshift(parent); - current = parent; + assert(this.isMutable(), 'final or mutable'); + for (let node of this.mutable) { + if (node.mutable !== undefined) { + node.mutable = Provable.toConstant(AccountUpdate, node.mutable); + } else { + node.final.hash = node.final.hash.toConstant(); + } + node.isDummy = Provable.toConstant(Bool, node.isDummy); + node.children.toConstantInPlace(); } - let context = { self: TokenId.default, caller: TokenId.default }; - for (let update of ancestors) { - if (update.body.mayUseToken.parentsOwnToken.toBoolean()) { - context.caller = context.self; - } else if (!update.body.mayUseToken.inheritFromParent.toBoolean()) { - context.caller = TokenId.default; + } + + print() { + let indent = 0; + let layout = ''; + + let toPretty = (a: UnfinishedForest) => { + if (a.isFinal()) { + layout += ' '.repeat(indent) + ' ( finalized forest )\n'; + return; + } + assert(a.isMutable(), 'final or mutable'); + indent += 2; + for (let tree of a.mutable) { + let label = tree.mutable?.label || ''; + if (tree.final !== undefined) { + Provable.asProver(() => (label = tree.final!.value.get().label)); + } + layout += ' '.repeat(indent) + `( ${label} )` + '\n'; + toPretty(tree.children); } - context.self = TokenId.derive(update.body.publicKey, update.body.tokenId); + indent -= 2; + }; + + toPretty(this); + console.log(layout); + } +} + +const UnfinishedTree = { + create(update: AccountUpdate | AccountUpdateTree): UnfinishedTree { + if (update instanceof AccountUpdate) { + return { + mutable: update, + id: update.id, + isDummy: update.isDummy(), + children: UnfinishedForest.empty(), + }; } - return context; + return { + final: update.accountUpdate, + id: update.id, + isDummy: Bool(false), + children: UnfinishedForest.fromForest(update.children), + }; }, - callerContextType: provablePure({ self: Field, caller: Field }), - computeCallDepth(update: AccountUpdate) { - for (let callDepth = 0; ; callDepth++) { - if (update.parent === undefined) return callDepth; - update = update.parent; + setTo(node: UnfinishedTree, update: AccountUpdate | AccountUpdateTree) { + if (update instanceof AccountUpdate) { + if (node.final !== undefined) { + Object.assign(node, { + mutable: update, + final: undefined, + children: UnfinishedForest.empty(), + }); + } + } else if (node.mutable !== undefined) { + Object.assign(node, { + mutable: undefined, + final: update.accountUpdate, + children: UnfinishedForest.fromForest(update.children), + }); } }, - map(updates: AccountUpdate[], map: (update: AccountUpdate) => AccountUpdate) { - let newUpdates: AccountUpdate[] = []; - for (let update of updates) { - let newUpdate = map(update); - newUpdate.children.accountUpdates = CallForest.map( - update.children.accountUpdates, - map - ); - newUpdates.push(newUpdate); - } - return newUpdates; + finalize(node: UnfinishedTree): AccountUpdateTreeBase & { isDummy: Bool } { + let accountUpdate = node.final ?? HashedAccountUpdate.hash(node.mutable); + let children = node.children.finalize(); + return { accountUpdate, id: node.id, isDummy: node.isDummy, children }; }, - forEach(updates: AccountUpdate[], callback: (update: AccountUpdate) => void) { - for (let update of updates) { - callback(update); - CallForest.forEach(update.children.accountUpdates, callback); - } + isUnfinished( + input: AccountUpdate | AccountUpdateTree | UnfinishedTree + ): input is UnfinishedTree { + return 'final' in input || 'mutable' in input; }, +}; + +class AccountUpdateLayout { + readonly map: Map; + readonly root: UnfinishedTree; + final?: AccountUpdateForest; + + constructor(root?: AccountUpdate) { + this.map = new Map(); + root ??= AccountUpdate.dummy(); + let rootTree: UnfinishedTree = { + mutable: root, + id: root.id, + isDummy: Bool(false), + children: UnfinishedForest.empty(), + }; + this.map.set(root.id, rootTree); + this.root = rootTree; + } + + get(update: AccountUpdate | AccountUpdateTree) { + return this.map.get(update.id); + } + + private getOrCreate( + update: AccountUpdate | AccountUpdateTree | UnfinishedTree + ): UnfinishedTree { + if (UnfinishedTree.isUnfinished(update)) { + if (!this.map.has(update.id)) { + this.map.set(update.id, update); + } + return update; + } + let node = this.map.get(update.id); + + if (node !== undefined) { + // might have to change node + UnfinishedTree.setTo(node, update); + return node; + } + + node = UnfinishedTree.create(update); + this.map.set(update.id, node); + return node; + } + + pushChild( + parent: AccountUpdate | UnfinishedTree, + child: AccountUpdate | AccountUpdateTree + ) { + let parentNode = this.getOrCreate(parent); + let childNode = this.getOrCreate(child); + parentNode.children.push(childNode); + } + + pushTopLevel(child: AccountUpdate) { + this.pushChild(this.root, child); + } + + setChildren( + parent: AccountUpdate | UnfinishedTree, + children: AccountUpdateForest + ) { + let parentNode = this.getOrCreate(parent); + parentNode.children.setToForest(children); + } + + setTopLevel(children: AccountUpdateForest) { + this.setChildren(this.root, children); + } + + disattach(update: AccountUpdate | AccountUpdateTree) { + let node = this.get(update); + node?.siblings?.remove(node); + return node; + } + + finalizeAndRemove(update: AccountUpdate | AccountUpdateTree) { + let node = this.get(update); + if (node === undefined) return; + this.disattach(update); + return node.children.finalize(); + } + + finalizeChildren() { + let final = this.root.children.finalize(); + this.final = final; + AccountUpdateForest.assertConstant(final); + return final; + } + + toFlatList({ mutate }: { mutate: boolean }) { + return this.root.children.toFlatArray(mutate); + } forEachPredecessor( - updates: AccountUpdate[], update: AccountUpdate, callback: (update: AccountUpdate) => void ) { - let isPredecessor = true; - CallForest.forEach(updates, (otherUpdate) => { - if (otherUpdate.id === update.id) isPredecessor = false; - if (isPredecessor) callback(otherUpdate); - }); - }, -}; + let updates = this.toFlatList({ mutate: false }); + for (let otherUpdate of updates) { + if (otherUpdate.id === update.id) return; + callback(otherUpdate); + } + } -function createChildAccountUpdate( - parent: AccountUpdate, - childAddress: PublicKey, - tokenId?: Field -) { - let child = AccountUpdate.defaultAccountUpdate(childAddress, tokenId); - makeChildAccountUpdate(parent, child); - return child; -} -function makeChildAccountUpdate(parent: AccountUpdate, child: AccountUpdate) { - child.body.callDepth = parent.body.callDepth + 1; - let wasChildAlready = parent.children.accountUpdates.find( - (update) => update.id === child.id - ); - // add to our children if not already here - if (!wasChildAlready) { - parent.children.accountUpdates.push(child); - // remove the child from the top level list / its current parent - AccountUpdate.unlink(child); + toConstantInPlace() { + this.root.children.toConstantInPlace(); } - child.parent = parent; } // authorization @@ -1819,69 +1800,14 @@ const Authorization = { accountUpdate.lazyAuthorization = undefined; return accountUpdate as AccountUpdateProved; }, - setLazySignature( - accountUpdate: AccountUpdate, - signature?: Omit - ) { - signature ??= {}; + setLazySignature(accountUpdate: AccountUpdate) { accountUpdate.body.authorizationKind.isSigned = Bool(true); accountUpdate.body.authorizationKind.isProved = Bool(false); accountUpdate.body.authorizationKind.verificationKeyHash = Field( mocks.dummyVerificationKeyHash ); accountUpdate.authorization = {}; - accountUpdate.lazyAuthorization = { ...signature, kind: 'lazy-signature' }; - }, - setProofAuthorizationKind( - { body, id }: AccountUpdate, - priorAccountUpdates?: AccountUpdate[] - ) { - body.authorizationKind.isSigned = Bool(false); - body.authorizationKind.isProved = Bool(true); - let hash = Provable.witness(Field, () => { - let proverData = zkAppProver.getData(); - let isProver = proverData !== undefined; - assert( - isProver || priorAccountUpdates !== undefined, - 'Called `setProofAuthorizationKind()` outside the prover without passing in `priorAccountUpdates`.' - ); - let myAccountUpdateId = isProver ? proverData.accountUpdate.id : id; - priorAccountUpdates ??= proverData.transaction.accountUpdates; - priorAccountUpdates = priorAccountUpdates.filter( - (a) => a.id !== myAccountUpdateId - ); - let priorAccountUpdatesFlat = CallForest.toFlatList( - priorAccountUpdates, - false - ); - let accountUpdate = [...priorAccountUpdatesFlat] - .reverse() - .find((body_) => - body_.update.verificationKey.isSome - .and(body_.tokenId.equals(body.tokenId)) - .and(body_.publicKey.equals(body.publicKey)) - .toBoolean() - ); - if (accountUpdate !== undefined) { - return accountUpdate.body.update.verificationKey.value.hash; - } - try { - let account = Mina.getAccount(body.publicKey, body.tokenId); - return account.zkapp?.verificationKey?.hash ?? Field(0); - } catch { - return Field(0); - } - }); - body.authorizationKind.verificationKeyHash = hash; - }, - setLazyProof( - accountUpdate: AccountUpdate, - proof: Omit, - priorAccountUpdates: AccountUpdate[] - ) { - Authorization.setProofAuthorizationKind(accountUpdate, priorAccountUpdates); - accountUpdate.authorization = {}; - accountUpdate.lazyAuthorization = { ...proof, kind: 'lazy-proof' }; + accountUpdate.lazyAuthorization = { kind: 'lazy-signature' }; }, setLazyNone(accountUpdate: AccountUpdate) { accountUpdate.body.authorizationKind.isSigned = Bool(false); @@ -1896,35 +1822,35 @@ const Authorization = { function addMissingSignatures( zkappCommand: ZkappCommand, - additionalKeys = [] as PrivateKey[] + privateKeys: PrivateKey[] ): ZkappCommandSigned { - let additionalPublicKeys = additionalKeys.map((sk) => sk.toPublicKey()); + let additionalPublicKeys = privateKeys.map((sk) => sk.toPublicKey()); let { commitment, fullCommitment } = transactionCommitments( - TypesBigint.ZkappCommand.fromJSON(ZkappCommand.toJSON(zkappCommand)) + TypesBigint.ZkappCommand.fromJSON(ZkappCommand.toJSON(zkappCommand)), + activeInstance.getNetworkId() ); function addFeePayerSignature(accountUpdate: FeePayerUnsigned): FeePayer { let { body, authorization, lazyAuthorization } = cloneCircuitValue(accountUpdate); if (lazyAuthorization === undefined) return { body, authorization }; - let { privateKey } = lazyAuthorization; - if (privateKey === undefined) { - let i = additionalPublicKeys.findIndex((pk) => - pk.equals(accountUpdate.body.publicKey).toBoolean() - ); - if (i === -1) { - // private key is missing, but we are not throwing an error here - // there is a change signature will be added by the wallet - // if not, error will be thrown by verifyAccountUpdate - // while .send() execution - return { body, authorization: dummySignature() }; - } - privateKey = additionalKeys[i]; + + let i = additionalPublicKeys.findIndex((pk) => + pk.equals(accountUpdate.body.publicKey).toBoolean() + ); + if (i === -1) { + // private key is missing, but we are not throwing an error here + // there is a change signature will be added by the wallet + // if not, error will be thrown by verifyAccountUpdate + // while .send() execution + return { body, authorization: dummySignature() }; } + let privateKey = privateKeys[i]; + let signature = signFieldElement( fullCommitment, privateKey.toBigInt(), - 'testnet' + activeInstance.getNetworkId() ); return { body, authorization: Signature.toBase58(signature) }; } @@ -1934,30 +1860,28 @@ function addMissingSignatures( if (accountUpdate.lazyAuthorization?.kind !== 'lazy-signature') { return accountUpdate as AccountUpdate & { lazyAuthorization?: LazyProof }; } - let { privateKey } = accountUpdate.lazyAuthorization; - if (privateKey === undefined) { - let i = additionalPublicKeys.findIndex((pk) => - pk.equals(accountUpdate.body.publicKey).toBoolean() - ); - if (i === -1) { - // private key is missing, but we are not throwing an error here - // there is a change signature will be added by the wallet - // if not, error will be thrown by verifyAccountUpdate - // while .send() execution - Authorization.setSignature(accountUpdate, dummySignature()); - return accountUpdate as AccountUpdate & { - lazyAuthorization: undefined; - }; - } - privateKey = additionalKeys[i]; + let i = additionalPublicKeys.findIndex((pk) => + pk.equals(accountUpdate.body.publicKey).toBoolean() + ); + if (i === -1) { + // private key is missing, but we are not throwing an error here + // there is a change signature will be added by the wallet + // if not, error will be thrown by verifyAccountUpdate + // while .send() execution + Authorization.setSignature(accountUpdate, dummySignature()); + return accountUpdate as AccountUpdate & { + lazyAuthorization: undefined; + }; } + let privateKey = privateKeys[i]; + let transactionCommitment = accountUpdate.body.useFullCommitment.toBoolean() ? fullCommitment : commitment; let signature = signFieldElement( transactionCommitment, privateKey.toBigInt(), - 'testnet' + activeInstance.getNetworkId() ); Authorization.setSignature(accountUpdate, Signature.toBase58(signature)); return accountUpdate as AccountUpdate & { lazyAuthorization: undefined }; @@ -1976,26 +1900,22 @@ function dummySignature() { /** * The public input for zkApps consists of certain hashes of the proving - AccountUpdate (and its child accountUpdates) which is constructed during method - execution. - - For SmartContract proving, a method is run twice: First outside the proof, to - obtain the public input, and once in the prover, which takes the public input - as input. The current transaction is hashed again inside the prover, which - asserts that the result equals the input public input, as part of the snark - circuit. The block producer will also hash the transaction they receive and - pass it as a public input to the verifier. Thus, the transaction is fully - constrained by the proof - the proof couldn't be used to attest to a different - transaction. + * account update (and its child updates) which is constructed during method execution. + * + * For SmartContract proving, a method is run twice: First outside the proof, to + * obtain the public input, and once in the prover, which takes the public input + * as input. The current transaction is hashed again inside the prover, which + * asserts that the result equals the input public input, as part of the snark + * circuit. The block producer will also hash the transaction they receive and + * pass it as a public input to the verifier. Thus, the transaction is fully + * constrained by the proof - the proof couldn't be used to attest to a different + * transaction. */ type ZkappPublicInput = { accountUpdate: Field; calls: Field; }; -let ZkappPublicInput = provablePure( - { accountUpdate: Field, calls: Field }, - { customObjectKeys: ['accountUpdate', 'calls'] } -); +let ZkappPublicInput = provablePure({ accountUpdate: Field, calls: Field }); async function addMissingProofs( zkappCommand: ZkappCommand, @@ -2070,7 +1990,7 @@ async function createZkappProof( }: LazyProof, { transaction, accountUpdate, index }: ZkappProverData ): Promise> { - let publicInput = accountUpdate.toPublicInput(); + let publicInput = accountUpdate.toPublicInput(transaction); let publicInputFields = MlFieldConstArray.to( ZkappPublicInput.toFields(publicInput) ); diff --git a/src/lib/account_update.unit-test.ts b/src/lib/mina/account-update.unit-test.ts similarity index 77% rename from src/lib/account_update.unit-test.ts rename to src/lib/mina/account-update.unit-test.ts index a280b22712..7f44a5dace 100644 --- a/src/lib/account_update.unit-test.ts +++ b/src/lib/mina/account-update.unit-test.ts @@ -1,4 +1,4 @@ -import { mocks } from '../bindings/crypto/constants.js'; +import { mocks } from '../../bindings/crypto/constants.js'; import { AccountUpdate, PrivateKey, @@ -6,9 +6,8 @@ import { Mina, Int64, Types, - Provable, -} from '../index.js'; -import { Test } from '../snarky.js'; +} from '../../index.js'; +import { Test } from '../../snarky.js'; import { expect } from 'expect'; let address = PrivateKey.random().toPublicKey(); @@ -52,18 +51,15 @@ function createAccountUpdate() { { let accountUpdate = createAccountUpdate(); - // TODO remove restriction "This function can't be run outside of a checked computation." - Provable.runAndCheck(() => { - let hash = accountUpdate.hash(); + let hash = accountUpdate.hash(); - // if we clone the accountUpdate, hash should be the same - let accountUpdate2 = AccountUpdate.clone(accountUpdate); - expect(accountUpdate2.hash()).toEqual(hash); + // if we clone the accountUpdate, hash should be the same + let accountUpdate2 = AccountUpdate.clone(accountUpdate); + expect(accountUpdate2.hash()).toEqual(hash); - // if we change something on the cloned accountUpdate, the hash should become different - AccountUpdate.setValue(accountUpdate2.update.appState[0], Field(1)); - expect(accountUpdate2.hash()).not.toEqual(hash); - }); + // if we change something on the cloned accountUpdate, the hash should become different + AccountUpdate.setValue(accountUpdate2.update.appState[0], Field(1)); + expect(accountUpdate2.hash()).not.toEqual(hash); } // converts account update to a public input that's consistent with the ocaml implementation @@ -71,12 +67,15 @@ function createAccountUpdate() { let otherAddress = PrivateKey.random().toPublicKey(); let accountUpdate = AccountUpdate.create(address); - accountUpdate.approve(AccountUpdate.create(otherAddress)); + let otherUpdate = AccountUpdate.create(otherAddress); + accountUpdate.approve(otherUpdate); - let publicInput = accountUpdate.toPublicInput(); + let publicInput = accountUpdate.toPublicInput({ + accountUpdates: [accountUpdate, otherUpdate], + }); // create transaction JSON with the same accountUpdate structure, for ocaml version - let tx = await Mina.transaction(() => { + let tx = await Mina.transaction(async () => { let accountUpdate = AccountUpdate.create(address); accountUpdate.approve(AccountUpdate.create(otherAddress)); }); @@ -113,10 +112,10 @@ function createAccountUpdate() { const feePayer = Local.testAccounts[0].publicKey; - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); }); - tx.sign(); + tx.sign([]); await expect(tx.send()).rejects.toThrow( 'Check signature: Invalid signature on fee payer for key' ); diff --git a/src/lib/mina/account.ts b/src/lib/mina/account.ts index 0969bedde3..e741412c93 100644 --- a/src/lib/mina/account.ts +++ b/src/lib/mina/account.ts @@ -1,135 +1,64 @@ import { Types } from '../../bindings/mina-transaction/types.js'; -import { Bool, Field } from '../core.js'; -import { Permissions } from '../account_update.js'; -import { UInt32, UInt64 } from '../int.js'; -import { PublicKey } from '../signature.js'; -import { TokenId, ReceiptChainHash } from '../base58-encodings.js'; +import { Bool, Field } from '../provable/wrapped.js'; +import { Permissions } from './account-update.js'; +import { UInt32, UInt64 } from '../provable/int.js'; +import { PublicKey } from '../provable/crypto/signature.js'; +import { TokenId, ReceiptChainHash } from './base58-encodings.js'; import { genericLayoutFold } from '../../bindings/lib/from-layout.js'; import { customTypes, TypeMap, } from '../../bindings/mina-transaction/gen/transaction.js'; import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; +import { ProvableExtended } from '../provable/types/struct.js'; +import { FetchedAccount } from './graphql.js'; -export { FetchedAccount, Account, PartialAccount }; -export { accountQuery, parseFetchedAccount, fillPartialAccount }; +export { Account, PartialAccount }; +export { newAccount, parseFetchedAccount, fillPartialAccount }; -type AuthRequired = Types.Json.AuthRequired; type Account = Types.Account; const Account = Types.Account; +function newAccount(accountId: { + publicKey: PublicKey; + tokenId?: Field; +}): Account { + let account = Account.empty(); + account.publicKey = accountId.publicKey; + account.tokenId = accountId.tokenId ?? Types.TokenId.empty(); + account.permissions = Permissions.initial(); + return account; +} + type PartialAccount = Omit, 'zkapp'> & { zkapp?: Partial; }; -// TODO auto-generate this type and the query -type FetchedAccount = { - publicKey: string; - token: string; - nonce: string; - balance: { total: string }; - tokenSymbol: string | null; - receiptChainHash: string | null; - timing: { - initialMinimumBalance: string | null; - cliffTime: string | null; - cliffAmount: string | null; - vestingPeriod: string | null; - vestingIncrement: string | null; - }; - permissions: { - editState: AuthRequired; - access: AuthRequired; - send: AuthRequired; - receive: AuthRequired; - setDelegate: AuthRequired; - setPermissions: AuthRequired; - setVerificationKey: { - auth: AuthRequired; - txnVersion: string; - }; - setZkappUri: AuthRequired; - editActionState: AuthRequired; - setTokenSymbol: AuthRequired; - incrementNonce: AuthRequired; - setVotingFor: AuthRequired; - setTiming: AuthRequired; - } | null; - delegateAccount: { publicKey: string } | null; - votingFor: string | null; - zkappState: string[] | null; - verificationKey: { verificationKey: string; hash: string } | null; - actionState: string[] | null; - provedState: boolean | null; - zkappUri: string | null; -}; -const accountQuery = (publicKey: string, tokenId: string) => `{ - account(publicKey: "${publicKey}", token: "${tokenId}") { - publicKey - token - nonce - balance { total } - tokenSymbol - receiptChainHash - timing { - initialMinimumBalance - cliffTime - cliffAmount - vestingPeriod - vestingIncrement - } - permissions { - editState - access - send - receive - setDelegate - setPermissions - setVerificationKey - setZkappUri - editActionState - setTokenSymbol - incrementNonce - setVotingFor - setTiming - } - delegateAccount { publicKey } - votingFor - zkappState - verificationKey { - verificationKey - hash - } - actionState - provedState - zkappUri - } -} -`; - // convert FetchedAccount (from graphql) to Account (internal representation both here and in Mina) -function parseFetchedAccount({ - publicKey, - nonce, - zkappState, - balance, - permissions, - timing: { - cliffAmount, - cliffTime, - initialMinimumBalance, - vestingIncrement, - vestingPeriod, - }, - delegateAccount, - receiptChainHash, - actionState, - token, - tokenSymbol, - verificationKey, - provedState, - zkappUri, -}: FetchedAccount): Account { +function parseFetchedAccount({ account }: FetchedAccount): Account { + const { + publicKey, + nonce, + zkappState, + balance, + permissions, + timing: { + cliffAmount, + cliffTime, + initialMinimumBalance, + vestingIncrement, + vestingPeriod, + }, + delegateAccount, + receiptChainHash, + actionState, + token, + tokenSymbol, + verificationKey, + provedState, + zkappUri, + } = account; + let hasZkapp = zkappState !== null || verificationKey !== null || @@ -187,19 +116,14 @@ function parseFetchedAccount({ } function fillPartialAccount(account: PartialAccount): Account { - return genericLayoutFold( + return genericLayoutFold>( TypeMap, customTypes, { map(type, value) { // if value exists, use it; otherwise fall back to dummy value if (value !== undefined) return value; - // fall back to dummy value - if (type.emptyValue) return type.emptyValue(); - return type.fromFields( - Array(type.sizeInFields()).fill(Field(0)), - type.toAuxiliary() - ); + return type.empty(); }, reduceArray(array) { return array; diff --git a/src/lib/base58-encodings.ts b/src/lib/mina/base58-encodings.ts similarity index 63% rename from src/lib/base58-encodings.ts rename to src/lib/mina/base58-encodings.ts index 52b3842330..f09786f38c 100644 --- a/src/lib/base58-encodings.ts +++ b/src/lib/mina/base58-encodings.ts @@ -1,5 +1,5 @@ -import { fieldEncodings } from './base58.js'; -import { Field } from './core.js'; +import { fieldEncodings } from '../util/base58.js'; +import { Field } from '../provable/wrapped.js'; export { TokenId, ReceiptChainHash, LedgerHash, EpochSeed, StateHash }; diff --git a/src/lib/mina/errors.ts b/src/lib/mina/errors.ts index d4ccd1ee4a..bb0deeefda 100644 --- a/src/lib/mina/errors.ts +++ b/src/lib/mina/errors.ts @@ -1,6 +1,6 @@ import { Types } from '../../bindings/mina-transaction/types.js'; -import { TokenId } from '../account_update.js'; -import { Int64 } from '../int.js'; +import { TokenId } from './account-update.js'; +import { Int64 } from '../provable/int.js'; export { invalidTransactionError }; @@ -60,23 +60,25 @@ function invalidTransactionError( ): string { let errorMessages = []; let rawErrors = JSON.stringify(errors); + let n = transaction.accountUpdates.length; - // handle errors for fee payer - let errorsForFeePayer = errors[0]; - for (let [error] of errorsForFeePayer) { - let message = ErrorHandlers[error as keyof typeof ErrorHandlers]?.({ - transaction, - accountUpdateIndex: NaN, - isFeePayer: true, - ...additionalContext, - }); - if (message) errorMessages.push(message); + // Check if the number of errors match the number of account updates. If there are more, then the fee payer has an error. + // We do this check because the fee payer error is not included in network transaction errors and is always present (even if empty) in the local transaction errors. + if (errors.length > n) { + let errorsForFeePayer = errors.shift() ?? []; + for (let [error] of errorsForFeePayer) { + let message = ErrorHandlers[error as keyof typeof ErrorHandlers]?.({ + transaction, + accountUpdateIndex: NaN, + isFeePayer: true, + ...additionalContext, + }); + if (message) errorMessages.push(message); + } } - // handle errors for each account update - let n = transaction.accountUpdates.length; - for (let i = 0; i < n; i++) { - let errorsForUpdate = errors[i + 1]; + for (let i = 0; i < errors.length; i++) { + let errorsForUpdate = errors[i]; for (let [error] of errorsForUpdate) { let message = ErrorHandlers[error as keyof typeof ErrorHandlers]?.({ transaction, diff --git a/src/lib/events.ts b/src/lib/mina/events.ts similarity index 89% rename from src/lib/events.ts rename to src/lib/mina/events.ts index 3e92a30389..5362c73e7c 100644 --- a/src/lib/events.ts +++ b/src/lib/mina/events.ts @@ -1,9 +1,9 @@ -import { prefixes } from '../bindings/crypto/constants.js'; -import { prefixToField } from '../bindings/lib/binable.js'; +import { prefixes } from '../../bindings/crypto/constants.js'; +import { prefixToField } from '../../bindings/lib/binable.js'; import { - GenericField, GenericProvableExtended, -} from '../bindings/lib/generic.js'; + GenericSignableField, +} from '../../bindings/lib/generic.js'; export { createEvents, dataAsHash }; @@ -15,7 +15,7 @@ function createEvents({ Field, Poseidon, }: { - Field: GenericField; + Field: GenericSignableField; Poseidon: Poseidon; }) { type Event = Field[]; @@ -60,7 +60,7 @@ function createEvents({ const EventsProvable = { ...Events, ...dataAsHash({ - emptyValue: Events.empty, + empty: Events.empty, toJSON(data: Field[][]) { return data.map((row) => row.map((e) => Field.toJSON(e))); }, @@ -107,7 +107,7 @@ function createEvents({ const SequenceEventsProvable = { ...Actions, ...dataAsHash({ - emptyValue: Actions.empty, + empty: Actions.empty, toJSON(data: Field[][]) { return data.map((row) => row.map((e) => Field.toJSON(e))); }, @@ -123,18 +123,16 @@ function createEvents({ } function dataAsHash({ - emptyValue, + empty, toJSON, fromJSON, }: { - emptyValue: () => { data: T; hash: Field }; + empty: () => { data: T; hash: Field }; toJSON: (value: T) => J; fromJSON: (json: J) => { data: T; hash: Field }; -}): GenericProvableExtended<{ data: T; hash: Field }, J, Field> & { - emptyValue(): { data: T; hash: Field }; -} { +}): GenericProvableExtended<{ data: T; hash: Field }, J, Field> { return { - emptyValue, + empty, sizeInFields() { return 1; }, @@ -142,7 +140,7 @@ function dataAsHash({ return [hash]; }, toAuxiliary(value) { - return [value?.data ?? emptyValue().data]; + return [value?.data ?? empty().data]; }, fromFields([hash], [data]) { return { data, hash }; diff --git a/src/lib/fetch.ts b/src/lib/mina/fetch.ts similarity index 69% rename from src/lib/fetch.ts rename to src/lib/mina/fetch.ts index 9ec5279104..a3f8c730f1 100644 --- a/src/lib/fetch.ts +++ b/src/lib/mina/fetch.ts @@ -1,24 +1,47 @@ import 'isomorphic-fetch'; -import { Field } from './core.js'; -import { UInt32, UInt64 } from './int.js'; -import { Actions, TokenId } from './account_update.js'; -import { PublicKey } from './signature.js'; +import { Field } from '../provable/wrapped.js'; +import { UInt32, UInt64 } from '../provable/int.js'; +import { Actions, TokenId } from './account-update.js'; +import { PublicKey, PrivateKey } from '../provable/crypto/signature.js'; import { NetworkValue } from './precondition.js'; -import { Types } from '../bindings/mina-transaction/types.js'; +import { Types } from '../../bindings/mina-transaction/types.js'; import { ActionStates } from './mina.js'; import { LedgerHash, EpochSeed, StateHash } from './base58-encodings.js'; import { Account, - accountQuery, - FetchedAccount, fillPartialAccount, parseFetchedAccount, PartialAccount, -} from './mina/account.js'; +} from './account.js'; +import { + type LastBlockQueryResponse, + type GenesisConstantsResponse, + type LastBlockQueryFailureCheckResponse, + type FetchedBlock, + type TransactionStatus, + type TransactionStatusQueryResponse, + type EventQueryResponse, + type ActionQueryResponse, + type EventActionFilterOptions, + type SendZkAppResponse, + type FetchedAccount, + type CurrentSlotResponse, + sendZkappQuery, + lastBlockQuery, + lastBlockQueryFailureCheck, + transactionStatusQuery, + getEventsQuery, + getActionsQuery, + genesisConstantsQuery, + accountQuery, + currentSlotQuery, +} from './graphql.js'; export { fetchAccount, fetchLastBlock, + fetchGenesisConstants, + fetchCurrentSlot, checkZkappTransaction, parseFetchedAccount, markAccountToBeFetched, @@ -26,11 +49,10 @@ export { markActionsToBeFetched, fetchMissingData, fetchTransactionStatus, - TransactionStatus, - EventActionFilterOptions, getCachedAccount, getCachedNetwork, getCachedActions, + getCachedGenesisConstants, addCachedAccount, networkConfig, setGraphqlEndpoint, @@ -38,11 +60,13 @@ export { setMinaGraphqlFallbackEndpoints, setArchiveGraphqlEndpoint, setArchiveGraphqlFallbackEndpoints, - sendZkappQuery, + setLightnetAccountManagerEndpoint, sendZkapp, - removeJsonQuotes, fetchEvents, fetchActions, + Lightnet, + type GenesisConstants, + type ActionStatesStringified, }; type NetworkConfig = { @@ -50,6 +74,7 @@ type NetworkConfig = { minaFallbackEndpoints: string[]; archiveEndpoint: string; archiveFallbackEndpoints: string[]; + lightnetAccountManagerEndpoint: string; }; let networkConfig = { @@ -57,6 +82,7 @@ let networkConfig = { minaFallbackEndpoints: [] as string[], archiveEndpoint: '', archiveFallbackEndpoints: [] as string[], + lightnetAccountManagerEndpoint: '', } satisfies NetworkConfig; function checkForValidUrl(url: string) { @@ -114,6 +140,20 @@ function setArchiveGraphqlFallbackEndpoints(graphqlEndpoints: string[]) { networkConfig.archiveFallbackEndpoints = graphqlEndpoints; } +/** + * Sets up the lightnet account manager endpoint to be used for accounts acquisition and releasing. + * + * @param endpoint Account manager endpoint. + */ +function setLightnetAccountManagerEndpoint(endpoint: string) { + if (!checkForValidUrl(endpoint)) { + throw new Error( + `Invalid account manager endpoint: ${endpoint}. Please specify a valid URL.` + ); + } + networkConfig.lightnetAccountManagerEndpoint = endpoint; +} + /** * Gets account information on the specified publicKey by performing a GraphQL query * to the specified endpoint. This will call the 'GetAccountInfo' query which fetches @@ -162,16 +202,15 @@ async function fetchAccountInternal( config?: FetchConfig ) { const { publicKey, tokenId } = accountInfo; - let [response, error] = await makeGraphqlRequest( + let [response, error] = await makeGraphqlRequest( accountQuery(publicKey, tokenId ?? TokenId.toBase58(TokenId.default)), graphqlEndpoint, networkConfig.minaFallbackEndpoints, config ); if (error !== undefined) return { account: undefined, error }; - let fetchedAccount = (response as FetchResponse).data - .account as FetchedAccount | null; - if (fetchedAccount === null) { + let fetchedAccount = response?.data; + if (!fetchedAccount) { return { account: undefined, error: { @@ -190,7 +229,7 @@ async function fetchAccountInternal( } type FetchConfig = { timeout?: number }; -type FetchResponse = { data: any; errors?: any }; +type FetchResponse = { data: TDataResponse; errors?: any }; type FetchError = { statusCode: number; statusText: string; @@ -239,6 +278,16 @@ let actionsToFetch = {} as Record< graphqlEndpoint: string; } >; +type GenesisConstants = { + genesisTimestamp: string; + coinbase: number; + accountCreationFee: number; + epochDuration: number; + k: number; + slotDuration: number; + slotsPerEpoch: number; +}; +let genesisConstantsCache = {} as Record; function markAccountToBeFetched( publicKey: PublicKey, @@ -315,6 +364,7 @@ async function fetchMissingData( (async () => { try { await fetchLastBlock(graphqlEndpoint); + await fetchGenesisConstants(graphqlEndpoint); delete networksToFetch[network[0]]; } catch {} })() @@ -345,6 +395,12 @@ function getCachedActions( ?.actions; } +function getCachedGenesisConstants( + graphqlEndpoint = networkConfig.minaEndpoint +): GenesisConstants { + return genesisConstantsCache[graphqlEndpoint]; +} + /** * Adds an account to the local cache, indexed by a GraphQL endpoint. */ @@ -392,7 +448,7 @@ function accountCacheKey( * Fetches the last block on the Mina network. */ async function fetchLastBlock(graphqlEndpoint = networkConfig.minaEndpoint) { - let [resp, error] = await makeGraphqlRequest( + let [resp, error] = await makeGraphqlRequest( lastBlockQuery, graphqlEndpoint, networkConfig.minaFallbackEndpoints @@ -411,82 +467,34 @@ async function fetchLastBlock(graphqlEndpoint = networkConfig.minaEndpoint) { return network; } -const lastBlockQuery = `{ - bestChain(maxLength: 1) { - protocolState { - blockchainState { - snarkedLedgerHash - stagedLedgerHash - date - utcDate - stagedLedgerProofEmitted - } - previousStateHash - consensusState { - blockHeight - slotSinceGenesis - slot - nextEpochData { - ledger {hash totalCurrency} - seed - startCheckpoint - lockCheckpoint - epochLength - } - stakingEpochData { - ledger {hash totalCurrency} - seed - startCheckpoint - lockCheckpoint - epochLength - } - epochCount - minWindowDensity - totalCurrency - epoch - } - } - } -}`; - -type LastBlockQueryFailureCheckResponse = { - bestChain: { - transactions: { - zkappCommands: { - hash: string; - failureReason: { - failures: string[]; - index: number; - }[]; - }[]; - }; - }[]; -}; - -const lastBlockQueryFailureCheck = `{ - bestChain(maxLength: 1) { - transactions { - zkappCommands { - hash - failureReason { - failures - index - } - } - } +async function fetchCurrentSlot(graphqlEndpoint = networkConfig.minaEndpoint) { + let [resp, error] = await makeGraphqlRequest( + currentSlotQuery, + graphqlEndpoint, + networkConfig.minaFallbackEndpoints + ); + if (error) throw Error(`Error making GraphQL request: ${error.statusText}`); + let bestChain = resp?.data?.bestChain; + if (!bestChain || bestChain.length === 0) { + throw Error( + 'Failed to fetch the current slot. The response data is undefined.' + ); } -}`; + return bestChain[0].protocolState.consensusState.slot; +} async function fetchLatestBlockZkappStatus( + blockLength: number, graphqlEndpoint = networkConfig.minaEndpoint ) { - let [resp, error] = await makeGraphqlRequest( - lastBlockQueryFailureCheck, - graphqlEndpoint, - networkConfig.minaFallbackEndpoints - ); + let [resp, error] = + await makeGraphqlRequest( + lastBlockQueryFailureCheck(blockLength), + graphqlEndpoint, + networkConfig.minaFallbackEndpoints + ); if (error) throw Error(`Error making GraphQL request: ${error.statusText}`); - let bestChain = resp?.data as LastBlockQueryFailureCheckResponse; + let bestChain = resp?.data; if (bestChain === undefined) { throw Error( 'Failed to fetch the latest zkApp transaction status. The response data is undefined.' @@ -495,19 +503,19 @@ async function fetchLatestBlockZkappStatus( return bestChain; } -async function checkZkappTransaction(txnId: string) { - let bestChainBlocks = await fetchLatestBlockZkappStatus(); - +async function checkZkappTransaction( + transactionHash: string, + blockLength = 20 +) { + let bestChainBlocks = await fetchLatestBlockZkappStatus(blockLength); for (let block of bestChainBlocks.bestChain) { for (let zkappCommand of block.transactions.zkappCommands) { - if (zkappCommand.hash === txnId) { + if (zkappCommand.hash === transactionHash) { if (zkappCommand.failureReason !== null) { let failureReason = zkappCommand.failureReason .reverse() .map((failure) => { - return ` AccountUpdate #${ - failure.index - } failed. Reason: "${failure.failures.join(', ')}"`; + return [failure.failures.map((failureItem) => failureItem)]; }); return { success: false, @@ -528,48 +536,6 @@ async function checkZkappTransaction(txnId: string) { }; } -type FetchedBlock = { - protocolState: { - blockchainState: { - snarkedLedgerHash: string; // hash-like encoding - stagedLedgerHash: string; // hash-like encoding - date: string; // String(Date.now()) - utcDate: string; // String(Date.now()) - stagedLedgerProofEmitted: boolean; // bool - }; - previousStateHash: string; // hash-like encoding - consensusState: { - blockHeight: string; // String(number) - slotSinceGenesis: string; // String(number) - slot: string; // String(number) - nextEpochData: { - ledger: { - hash: string; // hash-like encoding - totalCurrency: string; // String(number) - }; - seed: string; // hash-like encoding - startCheckpoint: string; // hash-like encoding - lockCheckpoint: string; // hash-like encoding - epochLength: string; // String(number) - }; - stakingEpochData: { - ledger: { - hash: string; // hash-like encoding - totalCurrency: string; // String(number) - }; - seed: string; // hash-like encoding - startCheckpoint: string; // hash-like encoding - lockCheckpoint: string; // hash-like encoding - epochLength: string; // String(number) - }; - epochCount: string; // String(number) - minWindowDensity: string; // String(number) - totalCurrency: string; // String(number) - epoch: string; // String(number) - }; - }; -}; - function parseFetchedBlock({ protocolState: { blockchainState: { snarkedLedgerHash, utcDate }, @@ -615,10 +581,6 @@ function parseEpochData({ }; } -const transactionStatusQuery = (txId: string) => `query { - transactionStatus(zkappTransaction:"${txId}") -}`; - /** * Fetches the status of a transaction. */ @@ -626,7 +588,7 @@ async function fetchTransactionStatus( txId: string, graphqlEndpoint = networkConfig.minaEndpoint ): Promise { - let [resp, error] = await makeGraphqlRequest( + let [resp, error] = await makeGraphqlRequest( transactionStatusQuery(txId), graphqlEndpoint, networkConfig.minaFallbackEndpoints @@ -639,16 +601,6 @@ async function fetchTransactionStatus( return txStatus as TransactionStatus; } -/** - * INCLUDED: A transaction that is on the longest chain - * - * PENDING: A transaction either in the transition frontier or in transaction pool but is not on the longest chain - * - * UNKNOWN: The transaction has either been snarked, reached finality through consensus or has been dropped - * - */ -type TransactionStatus = 'INCLUDED' | 'PENDING' | 'UNKNOWN'; - /** * Sends a zkApp command (transaction) to the specified GraphQL endpoint. */ @@ -657,7 +609,7 @@ function sendZkapp( graphqlEndpoint = networkConfig.minaEndpoint, { timeout = defaultTimeout } = {} ) { - return makeGraphqlRequest( + return makeGraphqlRequest( sendZkappQuery(json), graphqlEndpoint, networkConfig.minaFallbackEndpoints, @@ -667,141 +619,6 @@ function sendZkapp( ); } -// TODO: Decide an appropriate response structure. -function sendZkappQuery(json: string) { - return `mutation { - sendZkapp(input: { - zkappCommand: ${removeJsonQuotes(json)} - }) { - zkapp { - hash - id - failureReason { - failures - index - } - zkappCommand { - memo - feePayer { - body { - publicKey - } - } - accountUpdates { - body { - publicKey - useFullCommitment - incrementNonce - } - } - } - } - } -} -`; -} -type FetchedEvents = { - blockInfo: { - distanceFromMaxBlockHeight: number; - globalSlotSinceGenesis: number; - height: number; - stateHash: string; - parentHash: string; - chainStatus: string; - }; - eventData: { - transactionInfo: { - hash: string; - memo: string; - status: string; - }; - data: string[]; - }[]; -}; -type FetchedActions = { - blockInfo: { - distanceFromMaxBlockHeight: number; - }; - actionState: { - actionStateOne: string; - actionStateTwo: string; - }; - actionData: { - accountUpdateId: string; - data: string[]; - }[]; -}; - -type EventActionFilterOptions = { - to?: UInt32; - from?: UInt32; -}; - -const getEventsQuery = ( - publicKey: string, - tokenId: string, - filterOptions?: EventActionFilterOptions -) => { - const { to, from } = filterOptions ?? {}; - let input = `address: "${publicKey}", tokenId: "${tokenId}"`; - if (to !== undefined) { - input += `, to: ${to}`; - } - if (from !== undefined) { - input += `, from: ${from}`; - } - return `{ - events(input: { ${input} }) { - blockInfo { - distanceFromMaxBlockHeight - height - globalSlotSinceGenesis - stateHash - parentHash - chainStatus - } - eventData { - transactionInfo { - hash - memo - status - } - data - } - } -}`; -}; -const getActionsQuery = ( - publicKey: string, - actionStates: ActionStatesStringified, - tokenId: string, - _filterOptions?: EventActionFilterOptions -) => { - const { fromActionState, endActionState } = actionStates ?? {}; - let input = `address: "${publicKey}", tokenId: "${tokenId}"`; - if (fromActionState !== undefined) { - input += `, fromActionState: "${fromActionState}"`; - } - if (endActionState !== undefined) { - input += `, endActionState: "${endActionState}"`; - } - return `{ - actions(input: { ${input} }) { - blockInfo { - distanceFromMaxBlockHeight - } - actionState { - actionStateOne - actionStateTwo - } - actionData { - accountUpdateId - data - } - } -}`; -}; - /** * Asynchronously fetches event data for an account from the Mina Archive Node GraphQL API. * @async @@ -823,11 +640,11 @@ async function fetchEvents( filterOptions: EventActionFilterOptions = {} ) { if (!graphqlEndpoint) - throw new Error( - 'fetchEvents: Specified GraphQL endpoint is undefined. Please specify a valid endpoint.' + throw Error( + 'fetchEvents: Specified GraphQL endpoint is undefined. When using events, you must set the archive node endpoint in Mina.Network(). Please ensure your Mina.Network() configuration includes an archive node endpoint.' ); const { publicKey, tokenId } = accountInfo; - let [response, error] = await makeGraphqlRequest( + let [response, error] = await makeGraphqlRequest( getEventsQuery( publicKey, tokenId ?? TokenId.toBase58(TokenId.default), @@ -837,32 +654,13 @@ async function fetchEvents( networkConfig.archiveFallbackEndpoints ); if (error) throw Error(error.statusText); - let fetchedEvents = response?.data.events as FetchedEvents[]; + let fetchedEvents = response?.data.events; if (fetchedEvents === undefined) { throw Error( `Failed to fetch events data. Account: ${publicKey} Token: ${tokenId}` ); } - // TODO: This is a temporary fix. We should be able to fetch the event/action data from any block at the best tip. - // Once https://github.com/o1-labs/Archive-Node-API/issues/7 is resolved, we can remove this. - // If we have multiple blocks returned at the best tip (e.g. distanceFromMaxBlockHeight === 0), - // then filter out the blocks at the best tip. This is because we cannot guarantee that every block - // at the best tip will have the correct event data or guarantee that the specific block data will not - // fork in anyway. If this happens, we delay fetching event data until another block has been added to the network. - let numberOfBestTipBlocks = 0; - for (let i = 0; i < fetchedEvents.length; i++) { - if (fetchedEvents[i].blockInfo.distanceFromMaxBlockHeight === 0) { - numberOfBestTipBlocks++; - } - if (numberOfBestTipBlocks > 1) { - fetchedEvents = fetchedEvents.filter((event) => { - return event.blockInfo.distanceFromMaxBlockHeight !== 0; - }); - break; - } - } - return fetchedEvents.map((event) => { let events = event.eventData.map(({ data, transactionInfo }) => { return { @@ -891,21 +689,21 @@ async function fetchActions( graphqlEndpoint = networkConfig.archiveEndpoint ) { if (!graphqlEndpoint) - throw new Error( - 'fetchActions: Specified GraphQL endpoint is undefined. Please specify a valid endpoint.' + throw Error( + 'fetchActions: Specified GraphQL endpoint is undefined. When using actions, you must set the archive node endpoint in Mina.Network(). Please ensure your Mina.Network() configuration includes an archive node endpoint.' ); const { publicKey, actionStates, tokenId = TokenId.toBase58(TokenId.default), } = accountInfo; - let [response, error] = await makeGraphqlRequest( + let [response, error] = await makeGraphqlRequest( getActionsQuery(publicKey, actionStates, tokenId), graphqlEndpoint, networkConfig.archiveFallbackEndpoints ); if (error) throw Error(error.statusText); - let fetchedActions = response?.data.actions as FetchedActions[]; + let fetchedActions = response?.data.actions; if (fetchedActions === undefined) { return { error: { @@ -915,28 +713,7 @@ async function fetchActions( }; } - // TODO: This is a temporary fix. We should be able to fetch the event/action data from any block at the best tip. - // Once https://github.com/o1-labs/Archive-Node-API/issues/7 is resolved, we can remove this. - // If we have multiple blocks returned at the best tip (e.g. distanceFromMaxBlockHeight === 0), - // then filter out the blocks at the best tip. This is because we cannot guarantee that every block - // at the best tip will have the correct action data or guarantee that the specific block data will not - // fork in anyway. If this happens, we delay fetching action data until another block has been added to the network. - let numberOfBestTipBlocks = 0; - for (let i = 0; i < fetchedActions.length; i++) { - if (fetchedActions[i].blockInfo.distanceFromMaxBlockHeight === 0) { - numberOfBestTipBlocks++; - } - if (numberOfBestTipBlocks > 1) { - fetchedActions = fetchedActions.filter((action) => { - return action.blockInfo.distanceFromMaxBlockHeight !== 0; - }); - break; - } - } - // Archive Node API returns actions in the latest order, so we reverse the array to get the actions in chronological order. - fetchedActions.reverse(); let actionsList: { actions: string[][]; hash: string }[] = []; - // correct for archive node sending one block too many if ( fetchedActions.length !== 0 && @@ -993,19 +770,169 @@ async function fetchActions( return actionsList; } +/** + * Fetches genesis constants. + */ +async function fetchGenesisConstants( + graphqlEndpoint = networkConfig.minaEndpoint +): Promise { + let [resp, error] = await makeGraphqlRequest( + genesisConstantsQuery, + graphqlEndpoint, + networkConfig.minaFallbackEndpoints + ); + if (error) throw Error(error.statusText); + const genesisConstants = resp?.data?.genesisConstants; + const consensusConfiguration = + resp?.data?.daemonStatus?.consensusConfiguration; + if (genesisConstants === undefined || consensusConfiguration === undefined) { + throw Error('Failed to fetch genesis constants.'); + } + const data = { + genesisTimestamp: genesisConstants.genesisTimestamp, + coinbase: Number(genesisConstants.coinbase), + accountCreationFee: Number(genesisConstants.accountCreationFee), + epochDuration: Number(consensusConfiguration.epochDuration), + k: Number(consensusConfiguration.k), + slotDuration: Number(consensusConfiguration.slotDuration), + slotsPerEpoch: Number(consensusConfiguration.slotsPerEpoch), + }; + genesisConstantsCache[graphqlEndpoint] = data; + return data as GenesisConstants; +} + +namespace Lightnet { + /** + * Gets random key pair (public and private keys) from account manager + * that operates with accounts configured in target network Genesis Ledger. + * + * If an error is returned by the specified endpoint, an error is thrown. Otherwise, + * the data is returned. + * + * @param options.isRegularAccount Whether to acquire key pair of regular or zkApp account (one with already configured verification key) + * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from + * @returns Key pair + */ + export async function acquireKeyPair( + options: { + isRegularAccount?: boolean; + lightnetAccountManagerEndpoint?: string; + } = {} + ): Promise<{ + publicKey: PublicKey; + privateKey: PrivateKey; + }> { + const { + isRegularAccount = true, + lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, + } = options; + const response = await fetch( + `${lightnetAccountManagerEndpoint}/acquire-account?isRegularAccount=${isRegularAccount}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + } + ); + + if (response.ok) { + const data = await response.json(); + if (data) { + return { + publicKey: PublicKey.fromBase58(data.pk), + privateKey: PrivateKey.fromBase58(data.sk), + }; + } + } + + throw new Error('Failed to acquire the key pair'); + } + + /** + * Releases previously acquired key pair by public key. + * + * @param options.publicKey Public key of previously acquired key pair to release + * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from + * @returns Response message from the account manager as string or null if the request failed + */ + export async function releaseKeyPair(options: { + publicKey: string; + lightnetAccountManagerEndpoint?: string; + }): Promise { + const { + publicKey, + lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, + } = options; + const response = await fetch( + `${lightnetAccountManagerEndpoint}/release-account`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + pk: publicKey, + }), + } + ); + + if (response.ok) { + const data = await response.json(); + if (data) { + return data.message as string; + } + } + + return null; + } + + /** + * Gets previously acquired key pairs list. + * + * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from + * @returns Key pairs list or null if the request failed + */ + export async function listAcquiredKeyPairs(options: { + lightnetAccountManagerEndpoint?: string; + }): Promise | null> { + const { + lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, + } = options; + const response = await fetch( + `${lightnetAccountManagerEndpoint}/list-acquired-accounts`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + } + ); + + if (response.ok) { + const data = await response.json(); + if (data) { + return data.map((account: any) => ({ + publicKey: PublicKey.fromBase58(account.pk), + privateKey: PrivateKey.fromBase58(account.sk), + })); + } + } + + return null; + } +} + function updateActionState(actions: string[][], actionState: Field) { let actionHash = Actions.fromJSON(actions).hash; return Actions.updateSequenceState(actionState, actionHash); } -// removes the quotes on JSON keys -function removeJsonQuotes(json: string) { - let cleaned = JSON.stringify(JSON.parse(json), null, 2); - return cleaned.replace(/\"(\S+)\"\s*:/gm, '$1:'); -} - // TODO it seems we're not actually catching most errors here -async function makeGraphqlRequest( +async function makeGraphqlRequest( query: string, graphqlEndpoint = networkConfig.minaEndpoint, fallbackEndpoints: string[], @@ -1033,7 +960,7 @@ async function makeGraphqlRequest( body, signal: controller.signal, }); - return checkResponseStatus(response); + return checkResponseStatus(response); } finally { clearTimeouts(); } @@ -1059,7 +986,7 @@ async function makeGraphqlRequest( // If the request timed out, try the next 2 endpoints timeoutErrors.push({ url1, url2, error }); } else { - // If the request failed for some other reason (e.g. SnarkyJS error), return the error + // If the request failed for some other reason (e.g. o1js error), return the error return [undefined, error] as [undefined, FetchError]; } } @@ -1076,9 +1003,11 @@ async function makeGraphqlRequest( ]; } -async function checkResponseStatus( +async function checkResponseStatus( response: Response -): Promise<[FetchResponse, undefined] | [undefined, FetchError]> { +): Promise< + [FetchResponse, undefined] | [undefined, FetchError] +> { if (response.ok) { let jsonResponse = await response.json(); if (jsonResponse.errors && jsonResponse.errors.length > 0) { @@ -1100,7 +1029,7 @@ async function checkResponseStatus( } as FetchError, ]; } - return [jsonResponse as FetchResponse, undefined]; + return [jsonResponse as FetchResponse, undefined]; } else { return [ undefined, diff --git a/src/lib/fetch.unit-test.ts b/src/lib/mina/fetch.unit-test.ts similarity index 85% rename from src/lib/fetch.unit-test.ts rename to src/lib/mina/fetch.unit-test.ts index 49f0c22120..e61a7155a6 100644 --- a/src/lib/fetch.unit-test.ts +++ b/src/lib/mina/fetch.unit-test.ts @@ -1,5 +1,4 @@ -import { shutdown } from '../index.js'; -import * as Fetch from './fetch.js'; +import { removeJsonQuotes } from './graphql.js'; import { expect } from 'expect'; console.log('testing regex helpers'); @@ -22,7 +21,7 @@ expected = `{ ] }`; -actual = Fetch.removeJsonQuotes(input); +actual = removeJsonQuotes(input); expect(actual).toEqual(expected); input = `{ @@ -55,7 +54,7 @@ expected = `{ ] }`; -actual = Fetch.removeJsonQuotes(input); +actual = removeJsonQuotes(input); expect(actual).toEqual(expected); input = `{ @@ -74,7 +73,7 @@ expected = `{ Date: "2 May 2016 23:59:59" }`; -actual = Fetch.removeJsonQuotes(input); +actual = removeJsonQuotes(input); expect(actual).toEqual(expected); input = `{ @@ -93,7 +92,7 @@ expected = `{ Phone: "1234567890", Date: "2 May 2016 23:59:59" }`; -actual = Fetch.removeJsonQuotes(input); +actual = removeJsonQuotes(input); expect(actual).toEqual(expected); @@ -114,9 +113,8 @@ expected = `{ Date: "2 May 2016 23:59:59" }`; -actual = Fetch.removeJsonQuotes(input); +actual = removeJsonQuotes(input); expect(actual).toEqual(expected); console.log('regex tests complete 🎉'); -shutdown(); diff --git a/src/lib/mina/graphql.ts b/src/lib/mina/graphql.ts new file mode 100644 index 0000000000..f152c6e316 --- /dev/null +++ b/src/lib/mina/graphql.ts @@ -0,0 +1,508 @@ +import { UInt32 } from '../provable/int.js'; +import type { ZkappCommand } from './account-update.js'; +import type { ActionStatesStringified } from './fetch.js'; +import { Types } from '../../bindings/mina-transaction/types.js'; + +export { + type EpochData, + type LastBlockQueryResponse, + type GenesisConstantsResponse, + type FailureReasonResponse, + type LastBlockQueryFailureCheckResponse, + type FetchedBlock, + type TransactionStatus, + type TransactionStatusQueryResponse, + type EventQueryResponse, + type ActionQueryResponse, + type EventActionFilterOptions, + type SendZkAppResponse, + type FetchedAccount, + type CurrentSlotResponse, + getEventsQuery, + getActionsQuery, + sendZkappQuery, + transactionStatusQuery, + lastBlockQueryFailureCheck, + accountQuery, + currentSlotQuery, + genesisConstantsQuery, + lastBlockQuery, + removeJsonQuotes, +}; + +// removes the quotes on JSON keys +function removeJsonQuotes(json: string) { + let cleaned = JSON.stringify(JSON.parse(json), null, 2); + return cleaned.replace(/\"(\S+)\"\s*:/gm, '$1:'); +} + +type AuthRequired = Types.Json.AuthRequired; +// TODO auto-generate this type and the query +type FetchedAccount = { + account: { + publicKey: string; + token: string; + nonce: string; + balance: { total: string }; + tokenSymbol: string | null; + receiptChainHash: string | null; + timing: { + initialMinimumBalance: string | null; + cliffTime: string | null; + cliffAmount: string | null; + vestingPeriod: string | null; + vestingIncrement: string | null; + }; + permissions: { + editState: AuthRequired; + access: AuthRequired; + send: AuthRequired; + receive: AuthRequired; + setDelegate: AuthRequired; + setPermissions: AuthRequired; + setVerificationKey: { + auth: AuthRequired; + txnVersion: string; + }; + setZkappUri: AuthRequired; + editActionState: AuthRequired; + setTokenSymbol: AuthRequired; + incrementNonce: AuthRequired; + setVotingFor: AuthRequired; + setTiming: AuthRequired; + } | null; + delegateAccount: { publicKey: string } | null; + votingFor: string | null; + zkappState: string[] | null; + verificationKey: { verificationKey: string; hash: string } | null; + actionState: string[] | null; + provedState: boolean | null; + zkappUri: string | null; + }; +}; + +type EpochData = { + ledger: { + hash: string; + totalCurrency: string; + }; + seed: string; + startCheckpoint: string; + lockCheckpoint: string; + epochLength: string; +}; + +type LastBlockQueryResponse = { + bestChain: { + protocolState: { + blockchainState: { + snarkedLedgerHash: string; + stagedLedgerHash: string; + date: string; + utcDate: string; + stagedLedgerProofEmitted: boolean; + }; + previousStateHash: string; + consensusState: { + blockHeight: string; + slotSinceGenesis: string; + slot: string; + nextEpochData: EpochData; + stakingEpochData: EpochData; + epochCount: string; + minWindowDensity: string; + totalCurrency: string; + epoch: string; + }; + }; + }[]; +}; + +type FailureReasonResponse = { + failures: string[]; + index: number; +}[]; + +type LastBlockQueryFailureCheckResponse = { + bestChain: { + transactions: { + zkappCommands: { + hash: string; + failureReason: FailureReasonResponse; + }[]; + }; + }[]; +}; + +type FetchedBlock = { + protocolState: { + blockchainState: { + snarkedLedgerHash: string; // hash-like encoding + stagedLedgerHash: string; // hash-like encoding + date: string; // String(Date.now()) + utcDate: string; // String(Date.now()) + stagedLedgerProofEmitted: boolean; // bool + }; + previousStateHash: string; // hash-like encoding + consensusState: { + blockHeight: string; // String(number) + slotSinceGenesis: string; // String(number) + slot: string; // String(number) + nextEpochData: { + ledger: { + hash: string; // hash-like encoding + totalCurrency: string; // String(number) + }; + seed: string; // hash-like encoding + startCheckpoint: string; // hash-like encoding + lockCheckpoint: string; // hash-like encoding + epochLength: string; // String(number) + }; + stakingEpochData: { + ledger: { + hash: string; // hash-like encoding + totalCurrency: string; // String(number) + }; + seed: string; // hash-like encoding + startCheckpoint: string; // hash-like encoding + lockCheckpoint: string; // hash-like encoding + epochLength: string; // String(number) + }; + epochCount: string; // String(number) + minWindowDensity: string; // String(number) + totalCurrency: string; // String(number) + epoch: string; // String(number) + }; + }; +}; + +type GenesisConstantsResponse = { + genesisConstants: { + genesisTimestamp: string; + coinbase: string; + accountCreationFee: string; + }; + daemonStatus: { + consensusConfiguration: { + epochDuration: string; + k: string; + slotDuration: string; + slotsPerEpoch: string; + }; + }; +}; + +type CurrentSlotResponse = { + bestChain: Array<{ + protocolState: { + consensusState: { + slot: number; + }; + }; + }>; +}; + +/** + * INCLUDED: A transaction that is on the longest chain + * + * PENDING: A transaction either in the transition frontier or in transaction pool but is not on the longest chain + * + * UNKNOWN: The transaction has either been snarked, reached finality through consensus or has been dropped + * + */ +type TransactionStatus = 'INCLUDED' | 'PENDING' | 'UNKNOWN'; + +type TransactionStatusQueryResponse = { + transactionStatus: TransactionStatus; +}; + +type SendZkAppResponse = { + sendZkapp: { + zkapp: { + hash: string; + id: string; + zkappCommand: ZkappCommand; + failureReasons: FailureReasonResponse; + }; + }; +}; + +type EventQueryResponse = { + events: { + blockInfo: { + distanceFromMaxBlockHeight: number; + globalSlotSinceGenesis: number; + height: number; + stateHash: string; + parentHash: string; + chainStatus: string; + }; + eventData: { + transactionInfo: { + hash: string; + memo: string; + status: string; + }; + data: string[]; + }[]; + }[]; +}; + +type ActionQueryResponse = { + actions: { + blockInfo: { + distanceFromMaxBlockHeight: number; + }; + actionState: { + actionStateOne: string; + actionStateTwo: string; + }; + actionData: { + accountUpdateId: string; + data: string[]; + }[]; + }[]; +}; + +type EventActionFilterOptions = { + to?: UInt32; + from?: UInt32; +}; + +const transactionStatusQuery = (txId: string) => `query { + transactionStatus(zkappTransaction:"${txId}") + }`; + +const getEventsQuery = ( + publicKey: string, + tokenId: string, + filterOptions?: EventActionFilterOptions +) => { + const { to, from } = filterOptions ?? {}; + let input = `address: "${publicKey}", tokenId: "${tokenId}"`; + if (to !== undefined) { + input += `, to: ${to}`; + } + if (from !== undefined) { + input += `, from: ${from}`; + } + return `{ + events(input: { ${input} }) { + blockInfo { + distanceFromMaxBlockHeight + height + globalSlotSinceGenesis + stateHash + parentHash + chainStatus + } + eventData { + transactionInfo { + hash + memo + status + } + data + } + } +}`; +}; + +const getActionsQuery = ( + publicKey: string, + actionStates: ActionStatesStringified, + tokenId: string, + _filterOptions?: EventActionFilterOptions +) => { + const { fromActionState, endActionState } = actionStates ?? {}; + let input = `address: "${publicKey}", tokenId: "${tokenId}"`; + if (fromActionState !== undefined) { + input += `, fromActionState: "${fromActionState}"`; + } + if (endActionState !== undefined) { + input += `, endActionState: "${endActionState}"`; + } + return `{ + actions(input: { ${input} }) { + blockInfo { + distanceFromMaxBlockHeight + } + actionState { + actionStateOne + actionStateTwo + } + actionData { + accountUpdateId + data + } + } +}`; +}; + +const genesisConstantsQuery = `{ + genesisConstants { + genesisTimestamp + coinbase + accountCreationFee + } + daemonStatus { + consensusConfiguration { + epochDuration + k + slotDuration + slotsPerEpoch + } + } + }`; + +const lastBlockQuery = `{ + bestChain(maxLength: 1) { + protocolState { + blockchainState { + snarkedLedgerHash + stagedLedgerHash + date + utcDate + stagedLedgerProofEmitted + } + previousStateHash + consensusState { + blockHeight + slotSinceGenesis + slot + nextEpochData { + ledger {hash totalCurrency} + seed + startCheckpoint + lockCheckpoint + epochLength + } + stakingEpochData { + ledger {hash totalCurrency} + seed + startCheckpoint + lockCheckpoint + epochLength + } + epochCount + minWindowDensity + totalCurrency + epoch + } + } + } +}`; + +const lastBlockQueryFailureCheck = (length: number) => `{ + bestChain(maxLength: ${length}) { + transactions { + zkappCommands { + hash + failureReason { + failures + index + } + } + } + stateHash + protocolState { + consensusState { + blockHeight + epoch + slotSinceGenesis + } + previousStateHash + } + } +}`; + +// TODO: Decide an appropriate response structure. +function sendZkappQuery(json: string) { + return `mutation { + sendZkapp(input: { + zkappCommand: ${removeJsonQuotes(json)} + }) { + zkapp { + hash + id + failureReason { + failures + index + } + zkappCommand { + memo + feePayer { + body { + publicKey + } + } + accountUpdates { + body { + publicKey + useFullCommitment + incrementNonce + } + } + } + } + } +} +`; +} + +const accountQuery = (publicKey: string, tokenId: string) => `{ + account(publicKey: "${publicKey}", token: "${tokenId}") { + publicKey + token + nonce + balance { total } + tokenSymbol + receiptChainHash + timing { + initialMinimumBalance + cliffTime + cliffAmount + vestingPeriod + vestingIncrement + } + permissions { + editState + access + send + receive + setDelegate + setPermissions + setVerificationKey { + auth + txnVersion + } + setZkappUri + editActionState + setTokenSymbol + incrementNonce + setVotingFor + setTiming + } + delegateAccount { publicKey } + votingFor + zkappState + verificationKey { + verificationKey + hash + } + actionState + provedState + zkappUri + } +} +`; + +const currentSlotQuery = `{ + bestChain(maxLength: 1) { + protocolState { + consensusState { + slot + } + } + } +}`; diff --git a/src/lib/hash-input.unit-test.ts b/src/lib/mina/hash-input.unit-test.ts similarity index 87% rename from src/lib/hash-input.unit-test.ts rename to src/lib/mina/hash-input.unit-test.ts index 247029c2c7..3f22d65d62 100644 --- a/src/lib/hash-input.unit-test.ts +++ b/src/lib/mina/hash-input.unit-test.ts @@ -1,27 +1,23 @@ import { - isReady, AccountUpdate, Types, Permissions, - shutdown, ProvableExtended, -} from '../index.js'; +} from '../../index.js'; import { expect } from 'expect'; -import { jsLayout } from '../bindings/mina-transaction/gen/js-layout.js'; +import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; import { Json, provableFromLayout, -} from '../bindings/mina-transaction/gen/transaction.js'; -import { packToFields } from './hash.js'; -import { Random, test } from './testing/property.js'; -import { MlHashInput } from './ml/conversion.js'; -import { MlFieldConstArray } from './ml/fields.js'; -import { Test } from '../snarky.js'; +} from '../../bindings/mina-transaction/gen/transaction.js'; +import { packToFields } from '../provable/crypto/poseidon.js'; +import { Random, test } from '../testing/property.js'; +import { MlHashInput } from '../ml/conversion.js'; +import { MlFieldConstArray } from '../ml/fields.js'; +import { Test } from '../../snarky.js'; let { hashInputFromJson } = Test; -await isReady; - // types type Body = Types.AccountUpdate['body']; type Update = Body['update']; @@ -46,7 +42,7 @@ let NetworkPrecondition = provableFromLayout( ); let Body = provableFromLayout(bodyLayout as any); -// test with random account udpates +// test with random account updates test(Random.json.accountUpdate, (accountUpdateJson) => { fixVerificationKey(accountUpdateJson); let accountUpdate = AccountUpdate.fromJSON(accountUpdateJson); @@ -96,7 +92,6 @@ test(Random.json.accountUpdate, (accountUpdateJson) => { }); console.log('all hash inputs are consistent! 🎉'); -shutdown(); function testInput( Module: ProvableExtended, @@ -109,7 +104,7 @@ function testInput( let input2 = Module.toInput(value); let input1Json = JSON.stringify(input1); let input2Json = JSON.stringify(input2); - // console.log('snarkyjs', input2Json); + // console.log('o1js', input2Json); // console.log(); // console.log('protocol', input1Json); let ok1 = input1Json === input2Json; diff --git a/src/lib/mina/local-blockchain.ts b/src/lib/mina/local-blockchain.ts new file mode 100644 index 0000000000..9cbb2d9c57 --- /dev/null +++ b/src/lib/mina/local-blockchain.ts @@ -0,0 +1,394 @@ +import { SimpleLedger } from './transaction-logic/ledger.js'; +import { Ml } from '../ml/conversion.js'; +import { transactionCommitments } from '../../mina-signer/src/sign-zkapp-command.js'; +import { Ledger, Test } from '../../snarky.js'; +import { Field } from '../provable/wrapped.js'; +import { UInt32, UInt64 } from '../provable/int.js'; +import { PrivateKey, PublicKey } from '../provable/crypto/signature.js'; +import { Account } from './account.js'; +import { + ZkappCommand, + TokenId, + Authorization, + Actions, +} from './account-update.js'; +import { NetworkId } from '../../mina-signer/src/types.js'; +import { Types, TypesBigint } from '../../bindings/mina-transaction/types.js'; +import { invalidTransactionError } from './errors.js'; +import { + Transaction, + PendingTransaction, + createTransaction, + createIncludedTransaction, + createRejectedTransaction, + IncludedTransaction, + RejectedTransaction, + PendingTransactionStatus, +} from './transaction.js'; +import { + type FeePayerSpec, + type ActionStates, + Mina, + defaultNetworkConstants, +} from './mina-instance.js'; +import { + reportGetAccountError, + defaultNetworkState, + verifyTransactionLimits, + verifyAccountUpdate, +} from './transaction-validation.js'; + +export { LocalBlockchain }; +/** + * A mock Mina blockchain running locally and useful for testing. + */ +function LocalBlockchain({ + proofsEnabled = true, + enforceTransactionLimits = true, + networkId = 'testnet' as NetworkId, +} = {}) { + const slotTime = 3 * 60 * 1000; + const startTime = Date.now(); + const genesisTimestamp = UInt64.from(startTime); + const ledger = Ledger.create(); + let networkState = defaultNetworkState(); + let minaNetworkId: NetworkId = networkId; + + function addAccount(publicKey: PublicKey, balance: string) { + ledger.addAccount(Ml.fromPublicKey(publicKey), balance); + } + + let testAccounts: { + publicKey: PublicKey; + privateKey: PrivateKey; + }[] = []; + + for (let i = 0; i < 10; ++i) { + let MINA = 10n ** 9n; + const largeValue = 1000n * MINA; + const k = PrivateKey.random(); + const pk = k.toPublicKey(); + addAccount(pk, largeValue.toString()); + testAccounts.push({ privateKey: k, publicKey: pk }); + } + + const events: Record = {}; + const actions: Record< + string, + Record + > = {}; + + return { + getNetworkId: () => minaNetworkId, + proofsEnabled, + getNetworkConstants() { + return { + ...defaultNetworkConstants, + genesisTimestamp, + }; + }, + currentSlot() { + return UInt32.from( + Math.ceil((new Date().valueOf() - startTime) / slotTime) + ); + }, + hasAccount(publicKey: PublicKey, tokenId: Field = TokenId.default) { + return !!ledger.getAccount( + Ml.fromPublicKey(publicKey), + Ml.constFromField(tokenId) + ); + }, + getAccount( + publicKey: PublicKey, + tokenId: Field = TokenId.default + ): Account { + let accountJson = ledger.getAccount( + Ml.fromPublicKey(publicKey), + Ml.constFromField(tokenId) + ); + if (accountJson === undefined) { + throw new Error( + reportGetAccountError(publicKey.toBase58(), TokenId.toBase58(tokenId)) + ); + } + return Types.Account.fromJSON(accountJson); + }, + getNetworkState() { + return networkState; + }, + async sendTransaction(txn: Transaction): Promise { + let zkappCommandJson = ZkappCommand.toJSON(txn.transaction); + let commitments = transactionCommitments( + TypesBigint.ZkappCommand.fromJSON(zkappCommandJson), + minaNetworkId + ); + + if (enforceTransactionLimits) verifyTransactionLimits(txn.transaction); + + // create an ad-hoc ledger to record changes to accounts within the transaction + let simpleLedger = SimpleLedger.create(); + + for (const update of txn.transaction.accountUpdates) { + let authIsProof = !!update.authorization.proof; + let kindIsProof = update.body.authorizationKind.isProved.toBoolean(); + // checks and edge case where a proof is expected, but the developer forgot to invoke await tx.prove() + // this resulted in an assertion OCaml error, which didn't contain any useful information + if (kindIsProof && !authIsProof) { + throw Error( + `The actual authorization does not match the expected authorization kind. Did you forget to invoke \`await tx.prove();\`?` + ); + } + + let account = simpleLedger.load(update.body); + + // the first time we encounter an account, use it from the persistent ledger + if (account === undefined) { + let accountJson = ledger.getAccount( + Ml.fromPublicKey(update.body.publicKey), + Ml.constFromField(update.body.tokenId) + ); + if (accountJson !== undefined) { + let storedAccount = Account.fromJSON(accountJson); + simpleLedger.store(storedAccount); + account = storedAccount; + } + } + + // TODO: verify account update even if the account doesn't exist yet, using a default initial account + if (account !== undefined) { + let publicInput = update.toPublicInput(txn.transaction); + await verifyAccountUpdate( + account, + update, + publicInput, + commitments, + this.proofsEnabled, + this.getNetworkId() + ); + simpleLedger.apply(update); + } + } + + let status: PendingTransactionStatus = 'pending'; + const errors: string[] = []; + try { + ledger.applyJsonTransaction( + JSON.stringify(zkappCommandJson), + defaultNetworkConstants.accountCreationFee.toString(), + JSON.stringify(networkState) + ); + } catch (err: any) { + status = 'rejected'; + try { + const errorMessages = JSON.parse(err.message); + const formattedError = invalidTransactionError( + txn.transaction, + errorMessages, + { + accountCreationFee: + defaultNetworkConstants.accountCreationFee.toString(), + } + ); + errors.push(formattedError); + } catch (parseError: any) { + const fallbackErrorMessage = + err.message || parseError.message || 'Unknown error occurred'; + errors.push(fallbackErrorMessage); + } + } + + // fetches all events from the transaction and stores them + // events are identified and associated with a publicKey and tokenId + txn.transaction.accountUpdates.forEach((p, i) => { + let pJson = zkappCommandJson.accountUpdates[i]; + let addr = pJson.body.publicKey; + let tokenId = pJson.body.tokenId; + events[addr] ??= {}; + if (p.body.events.data.length > 0) { + events[addr][tokenId] ??= []; + let updatedEvents = p.body.events.data.map((data) => { + return { + data, + transactionInfo: { + transactionHash: '', + transactionStatus: '', + transactionMemo: '', + }, + }; + }); + events[addr][tokenId].push({ + events: updatedEvents, + blockHeight: networkState.blockchainLength, + globalSlot: networkState.globalSlotSinceGenesis, + // The following fields are fetched from the Mina network. For now, we mock these values out + // since networkState does not contain these fields. + blockHash: '', + parentBlockHash: '', + chainStatus: '', + }); + } + + // actions/sequencing events + + // most recent action state + let storedActions = actions[addr]?.[tokenId]; + let latestActionState_ = + storedActions?.[storedActions.length - 1]?.hash; + // if there exists no hash, this means we initialize our latest hash with the empty state + let latestActionState = + latestActionState_ !== undefined + ? Field(latestActionState_) + : Actions.emptyActionState(); + + actions[addr] ??= {}; + if (p.body.actions.data.length > 0) { + let newActionState = Actions.updateSequenceState( + latestActionState, + p.body.actions.hash + ); + actions[addr][tokenId] ??= []; + actions[addr][tokenId].push({ + actions: pJson.body.actions, + hash: newActionState.toString(), + }); + } + }); + + const hash = Test.transactionHash.hashZkAppCommand(txn.toJSON()); + const pendingTransaction: Omit = + { + status, + errors, + transaction: txn.transaction, + hash, + toJSON: txn.toJSON, + toPretty: txn.toPretty, + }; + + const wait = async (_options?: { + maxAttempts?: number; + interval?: number; + }): Promise => { + const pendingTransaction = await safeWait(_options); + if (pendingTransaction.status === 'rejected') { + throw Error( + `Transaction failed with errors:\n${pendingTransaction.errors.join( + '\n' + )}` + ); + } + return pendingTransaction; + }; + + const safeWait = async (_options?: { + maxAttempts?: number; + interval?: number; + }): Promise => { + if (status === 'rejected') { + return createRejectedTransaction( + pendingTransaction, + pendingTransaction.errors + ); + } + return createIncludedTransaction(pendingTransaction); + }; + + return { + ...pendingTransaction, + wait, + safeWait, + }; + }, + async transaction(sender: FeePayerSpec, f: () => Promise) { + // TODO we run the transaction twice to match the behaviour of `Network.transaction` + let tx = await createTransaction(sender, f, 0, { + isFinalRunOutsideCircuit: false, + proofsEnabled: this.proofsEnabled, + fetchMode: 'test', + }); + let hasProofs = tx.transaction.accountUpdates.some( + Authorization.hasLazyProof + ); + return await createTransaction(sender, f, 1, { + isFinalRunOutsideCircuit: !hasProofs, + proofsEnabled: this.proofsEnabled, + }); + }, + applyJsonTransaction(json: string) { + return ledger.applyJsonTransaction( + json, + defaultNetworkConstants.accountCreationFee.toString(), + JSON.stringify(networkState) + ); + }, + async fetchEvents(publicKey: PublicKey, tokenId: Field = TokenId.default) { + // Return events in reverse chronological order (latest events at the beginning) + const reversedEvents = ( + events?.[publicKey.toBase58()]?.[TokenId.toBase58(tokenId)] ?? [] + ).reverse(); + return reversedEvents; + }, + async fetchActions( + publicKey: PublicKey, + actionStates?: ActionStates, + tokenId: Field = TokenId.default + ) { + return this.getActions(publicKey, actionStates, tokenId); + }, + getActions( + publicKey: PublicKey, + actionStates?: ActionStates, + tokenId: Field = TokenId.default + ): { hash: string; actions: string[][] }[] { + let currentActions = + actions?.[publicKey.toBase58()]?.[TokenId.toBase58(tokenId)] ?? []; + let { fromActionState, endActionState } = actionStates ?? {}; + + let emptyState = Actions.emptyActionState(); + if (endActionState?.equals(emptyState).toBoolean()) return []; + + let start = fromActionState?.equals(emptyState).toBoolean() + ? undefined + : fromActionState?.toString(); + let end = endActionState?.toString(); + + let startIndex = 0; + if (start) { + let i = currentActions.findIndex((e) => e.hash === start); + if (i === -1) throw Error(`getActions: fromActionState not found.`); + startIndex = i + 1; + } + let endIndex: number | undefined; + if (end) { + let i = currentActions.findIndex((e) => e.hash === end); + if (i === -1) throw Error(`getActions: endActionState not found.`); + endIndex = i + 1; + } + return currentActions.slice(startIndex, endIndex); + }, + addAccount, + /** + * An array of 10 test accounts that have been pre-filled with + * 30000000000 units of currency. + */ + testAccounts, + setGlobalSlot(slot: UInt32 | number) { + networkState.globalSlotSinceGenesis = UInt32.from(slot); + }, + incrementGlobalSlot(increment: UInt32 | number) { + networkState.globalSlotSinceGenesis = + networkState.globalSlotSinceGenesis.add(increment); + }, + setBlockchainLength(height: UInt32) { + networkState.blockchainLength = height; + }, + setTotalCurrency(currency: UInt64) { + networkState.totalCurrency = currency; + }, + setProofsEnabled(newProofsEnabled: boolean) { + this.proofsEnabled = newProofsEnabled; + }, + }; +} +// assert type compatibility without preventing LocalBlockchain to return additional properties / methods +LocalBlockchain satisfies (...args: any) => Mina; diff --git a/src/lib/mina/mina-instance.ts b/src/lib/mina/mina-instance.ts new file mode 100644 index 0000000000..87db6f24b0 --- /dev/null +++ b/src/lib/mina/mina-instance.ts @@ -0,0 +1,212 @@ +/** + * This module holds the global Mina instance and its interface. + */ +import { Field } from '../provable/wrapped.js'; +import { UInt64, UInt32 } from '../provable/int.js'; +import { PublicKey } from '../provable/crypto/signature.js'; +import type { EventActionFilterOptions } from '././../mina/graphql.js'; +import type { NetworkId } from '../../mina-signer/src/types.js'; +import type { Transaction, PendingTransaction } from './mina.js'; +import type { Account } from './account.js'; +import type { NetworkValue } from './precondition.js'; +import type * as Fetch from './fetch.js'; + +export { + Mina, + FeePayerSpec, + ActionStates, + NetworkConstants, + defaultNetworkConstants, + activeInstance, + setActiveInstance, + ZkappStateLength, + currentSlot, + getAccount, + hasAccount, + getBalance, + getNetworkId, + getNetworkConstants, + getNetworkState, + fetchEvents, + fetchActions, + getActions, + getProofsEnabled, +}; + +const defaultAccountCreationFee = 1_000_000_000; +const defaultNetworkConstants: NetworkConstants = { + genesisTimestamp: UInt64.from(0), + slotTime: UInt64.from(3 * 60 * 1000), + accountCreationFee: UInt64.from(defaultAccountCreationFee), +}; + +const ZkappStateLength = 8; + +/** + * Allows you to specify information about the fee payer account and the transaction. + */ +type FeePayerSpec = + | PublicKey + | { + sender: PublicKey; + fee?: number | string | UInt64; + memo?: string; + nonce?: number; + } + | undefined; + +type ActionStates = { + fromActionState?: Field; + endActionState?: Field; +}; + +type NetworkConstants = { + genesisTimestamp: UInt64; + /** + * Duration of 1 slot in millisecondw + */ + slotTime: UInt64; + accountCreationFee: UInt64; +}; + +type Mina = { + transaction( + sender: FeePayerSpec, + f: () => Promise + ): Promise; + currentSlot(): UInt32; + hasAccount(publicKey: PublicKey, tokenId?: Field): boolean; + getAccount(publicKey: PublicKey, tokenId?: Field): Account; + getNetworkState(): NetworkValue; + getNetworkConstants(): NetworkConstants; + sendTransaction(transaction: Transaction): Promise; + fetchEvents: ( + publicKey: PublicKey, + tokenId?: Field, + filterOptions?: EventActionFilterOptions + ) => ReturnType; + fetchActions: ( + publicKey: PublicKey, + actionStates?: ActionStates, + tokenId?: Field + ) => ReturnType; + getActions: ( + publicKey: PublicKey, + actionStates?: ActionStates, + tokenId?: Field + ) => { hash: string; actions: string[][] }[]; + proofsEnabled: boolean; + getNetworkId(): NetworkId; +}; + +let activeInstance: Mina = { + getNetworkConstants: () => defaultNetworkConstants, + currentSlot: noActiveInstance, + hasAccount: noActiveInstance, + getAccount: noActiveInstance, + getNetworkState: noActiveInstance, + sendTransaction: noActiveInstance, + transaction: noActiveInstance, + fetchEvents: noActiveInstance, + fetchActions: noActiveInstance, + getActions: noActiveInstance, + proofsEnabled: true, + getNetworkId: () => 'testnet', +}; + +/** + * Set the currently used Mina instance. + */ +function setActiveInstance(m: Mina) { + activeInstance = m; +} + +function noActiveInstance(): never { + throw Error('Must call Mina.setActiveInstance first'); +} + +/** + * @return The current slot number, according to the active Mina instance. + */ +function currentSlot(): UInt32 { + return activeInstance.currentSlot(); +} + +/** + * @return The account data associated to the given public key. + */ +function getAccount(publicKey: PublicKey, tokenId?: Field): Account { + return activeInstance.getAccount(publicKey, tokenId); +} + +/** + * Checks if an account exists within the ledger. + */ +function hasAccount(publicKey: PublicKey, tokenId?: Field): boolean { + return activeInstance.hasAccount(publicKey, tokenId); +} + +/** + * @return The current Mina network ID. + */ +function getNetworkId() { + return activeInstance.getNetworkId(); +} + +/** + * @return Data associated with the current Mina network constants. + */ +function getNetworkConstants() { + return activeInstance.getNetworkConstants(); +} + +/** + * @return Data associated with the current state of the Mina network. + */ +function getNetworkState() { + return activeInstance.getNetworkState(); +} + +/** + * @return The balance associated to the given public key. + */ +function getBalance(publicKey: PublicKey, tokenId?: Field) { + return activeInstance.getAccount(publicKey, tokenId).balance; +} + +/** + * @return A list of emitted events associated to the given public key. + */ +async function fetchEvents( + publicKey: PublicKey, + tokenId: Field, + filterOptions: EventActionFilterOptions = {} +) { + return await activeInstance.fetchEvents(publicKey, tokenId, filterOptions); +} + +/** + * @return A list of emitted sequencing actions associated to the given public key. + */ +async function fetchActions( + publicKey: PublicKey, + actionStates?: ActionStates, + tokenId?: Field +) { + return await activeInstance.fetchActions(publicKey, actionStates, tokenId); +} + +/** + * @return A list of emitted sequencing actions associated to the given public key. + */ +function getActions( + publicKey: PublicKey, + actionStates?: ActionStates, + tokenId?: Field +) { + return activeInstance.getActions(publicKey, actionStates, tokenId); +} + +function getProofsEnabled() { + return activeInstance.proofsEnabled; +} diff --git a/src/lib/mina/mina.ts b/src/lib/mina/mina.ts new file mode 100644 index 0000000000..0f843c76a0 --- /dev/null +++ b/src/lib/mina/mina.ts @@ -0,0 +1,538 @@ +import { Test } from '../../snarky.js'; +import { Field } from '../provable/wrapped.js'; +import { UInt64 } from '../provable/int.js'; +import { PublicKey } from '../provable/crypto/signature.js'; +import { ZkappCommand, TokenId, Authorization } from './account-update.js'; +import * as Fetch from './fetch.js'; +import { invalidTransactionError } from './errors.js'; +import { Types } from '../../bindings/mina-transaction/types.js'; +import { Account } from './account.js'; +import { NetworkId } from '../../mina-signer/src/types.js'; +import { currentTransaction } from './transaction-context.js'; +import { + type FeePayerSpec, + type ActionStates, + type NetworkConstants, + activeInstance, + setActiveInstance, + Mina, + defaultNetworkConstants, + currentSlot, + getAccount, + hasAccount, + getBalance, + getNetworkId, + getNetworkConstants, + getNetworkState, + fetchEvents, + fetchActions, + getActions, + getProofsEnabled, +} from './mina-instance.js'; +import { type EventActionFilterOptions } from './graphql.js'; +import { + type Transaction, + type PendingTransaction, + type IncludedTransaction, + type RejectedTransaction, + type PendingTransactionStatus, + createTransaction, + newTransaction, + transaction, + createRejectedTransaction, + createIncludedTransaction, +} from './transaction.js'; +import { + reportGetAccountError, + verifyTransactionLimits, + defaultNetworkState, + filterGroups, +} from './transaction-validation.js'; +import { LocalBlockchain } from './local-blockchain.js'; + +export { + LocalBlockchain, + Network, + currentTransaction, + Transaction, + type PendingTransaction, + type IncludedTransaction, + type RejectedTransaction, + type PendingTransactionStatus, + activeInstance, + setActiveInstance, + transaction, + sender, + currentSlot, + getAccount, + hasAccount, + getBalance, + getNetworkId, + getNetworkConstants, + getNetworkState, + fetchEvents, + fetchActions, + getActions, + FeePayerSpec, + ActionStates, + faucet, + waitForFunding, + getProofsEnabled, + // for internal testing only + filterGroups, + type NetworkConstants, +}; + +// patch active instance so that we can still create basic transactions without giving Mina network details +setActiveInstance({ + ...activeInstance, + async transaction(sender: FeePayerSpec, f: () => Promise) { + return await createTransaction(sender, f, 0); + }, +}); + +const Transaction = { + fromJSON(json: Types.Json.ZkappCommand): Transaction { + let transaction = ZkappCommand.fromJSON(json); + return newTransaction(transaction, activeInstance.proofsEnabled); + }, +}; + +/** + * Represents the Mina blockchain running on a real network + */ +function Network(graphqlEndpoint: string): Mina; +function Network(options: { + networkId?: NetworkId; + mina: string | string[]; + archive?: string | string[]; + lightnetAccountManager?: string; +}): Mina; +function Network( + options: + | { + networkId?: NetworkId; + mina: string | string[]; + archive?: string | string[]; + lightnetAccountManager?: string; + } + | string +): Mina { + let minaNetworkId: NetworkId = 'testnet'; + let minaGraphqlEndpoint: string; + let archiveEndpoint: string; + let lightnetAccountManagerEndpoint: string; + + if (options && typeof options === 'string') { + minaGraphqlEndpoint = options; + Fetch.setGraphqlEndpoint(minaGraphqlEndpoint); + } else if (options && typeof options === 'object') { + if (options.networkId) { + minaNetworkId = options.networkId; + } + if (!options.mina) + throw new Error( + "Network: malformed input. Please provide an object with 'mina' endpoint." + ); + if (Array.isArray(options.mina) && options.mina.length !== 0) { + minaGraphqlEndpoint = options.mina[0]; + Fetch.setGraphqlEndpoint(minaGraphqlEndpoint); + Fetch.setMinaGraphqlFallbackEndpoints(options.mina.slice(1)); + } else if (typeof options.mina === 'string') { + minaGraphqlEndpoint = options.mina; + Fetch.setGraphqlEndpoint(minaGraphqlEndpoint); + } + + if (options.archive !== undefined) { + if (Array.isArray(options.archive) && options.archive.length !== 0) { + archiveEndpoint = options.archive[0]; + Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); + Fetch.setArchiveGraphqlFallbackEndpoints(options.archive.slice(1)); + } else if (typeof options.archive === 'string') { + archiveEndpoint = options.archive; + Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); + } + } + + if ( + options.lightnetAccountManager !== undefined && + typeof options.lightnetAccountManager === 'string' + ) { + lightnetAccountManagerEndpoint = options.lightnetAccountManager; + Fetch.setLightnetAccountManagerEndpoint(lightnetAccountManagerEndpoint); + } + } else { + throw new Error( + "Network: malformed input. Please provide a string or an object with 'mina' and 'archive' endpoints." + ); + } + + return { + getNetworkId: () => minaNetworkId, + getNetworkConstants() { + if (currentTransaction()?.fetchMode === 'test') { + Fetch.markNetworkToBeFetched(minaGraphqlEndpoint); + const genesisConstants = + Fetch.getCachedGenesisConstants(minaGraphqlEndpoint); + return genesisConstants !== undefined + ? genesisToNetworkConstants(genesisConstants) + : defaultNetworkConstants; + } + if ( + !currentTransaction.has() || + currentTransaction.get().fetchMode === 'cached' + ) { + const genesisConstants = + Fetch.getCachedGenesisConstants(minaGraphqlEndpoint); + if (genesisConstants !== undefined) + return genesisToNetworkConstants(genesisConstants); + } + return defaultNetworkConstants; + }, + currentSlot() { + throw Error( + 'currentSlot() is not implemented yet for remote blockchains.' + ); + }, + hasAccount(publicKey: PublicKey, tokenId: Field = TokenId.default) { + if ( + !currentTransaction.has() || + currentTransaction.get().fetchMode === 'cached' + ) { + return !!Fetch.getCachedAccount( + publicKey, + tokenId, + minaGraphqlEndpoint + ); + } + return false; + }, + getAccount(publicKey: PublicKey, tokenId: Field = TokenId.default) { + if (currentTransaction()?.fetchMode === 'test') { + Fetch.markAccountToBeFetched(publicKey, tokenId, minaGraphqlEndpoint); + let account = Fetch.getCachedAccount( + publicKey, + tokenId, + minaGraphqlEndpoint + ); + return account ?? dummyAccount(publicKey); + } + if ( + !currentTransaction.has() || + currentTransaction.get().fetchMode === 'cached' + ) { + let account = Fetch.getCachedAccount( + publicKey, + tokenId, + minaGraphqlEndpoint + ); + if (account !== undefined) return account; + } + throw Error( + `${reportGetAccountError( + publicKey.toBase58(), + TokenId.toBase58(tokenId) + )}\nGraphql endpoint: ${minaGraphqlEndpoint}` + ); + }, + getNetworkState() { + if (currentTransaction()?.fetchMode === 'test') { + Fetch.markNetworkToBeFetched(minaGraphqlEndpoint); + let network = Fetch.getCachedNetwork(minaGraphqlEndpoint); + return network ?? defaultNetworkState(); + } + if ( + !currentTransaction.has() || + currentTransaction.get().fetchMode === 'cached' + ) { + let network = Fetch.getCachedNetwork(minaGraphqlEndpoint); + if (network !== undefined) return network; + } + throw Error( + `getNetworkState: Could not fetch network state from graphql endpoint ${minaGraphqlEndpoint} outside of a transaction.` + ); + }, + async sendTransaction(txn: Transaction): Promise { + verifyTransactionLimits(txn.transaction); + + let [response, error] = await Fetch.sendZkapp(txn.toJSON()); + let errors: string[] = []; + if (response === undefined && error !== undefined) { + errors = [JSON.stringify(error)]; + } else if (response && response.errors && response.errors.length > 0) { + response?.errors.forEach((e: any) => errors.push(JSON.stringify(e))); + } + + const status: PendingTransactionStatus = + errors.length === 0 ? 'pending' : 'rejected'; + const hash = Test.transactionHash.hashZkAppCommand(txn.toJSON()); + const pendingTransaction: Omit = + { + status, + data: response?.data, + errors, + transaction: txn.transaction, + hash, + toJSON: txn.toJSON, + toPretty: txn.toPretty, + }; + + const pollTransactionStatus = async ( + transactionHash: string, + maxAttempts: number, + interval: number, + attempts: number = 0 + ): Promise => { + let res: Awaited>; + try { + res = await Fetch.checkZkappTransaction(transactionHash); + if (res.success) { + return createIncludedTransaction(pendingTransaction); + } else if (res.failureReason) { + const error = invalidTransactionError( + txn.transaction, + res.failureReason, + { + accountCreationFee: + defaultNetworkConstants.accountCreationFee.toString(), + } + ); + return createRejectedTransaction(pendingTransaction, [error]); + } + } catch (error) { + return createRejectedTransaction(pendingTransaction, [ + (error as Error).message, + ]); + } + + if (maxAttempts && attempts >= maxAttempts) { + return createRejectedTransaction(pendingTransaction, [ + `Exceeded max attempts.\nTransactionId: ${transactionHash}\nAttempts: ${attempts}\nLast received status: ${res}`, + ]); + } + + await new Promise((resolve) => setTimeout(resolve, interval)); + return pollTransactionStatus( + transactionHash, + maxAttempts, + interval, + attempts + 1 + ); + }; + + // default is 45 attempts * 20s each = 15min + // the block time on berkeley is currently longer than the average 3-4min, so its better to target a higher block time + // fetching an update every 20s is more than enough with a current block time of 3min + const poll = async ( + maxAttempts: number = 45, + interval: number = 20000 + ): Promise => { + return pollTransactionStatus(hash, maxAttempts, interval); + }; + + const wait = async (options?: { + maxAttempts?: number; + interval?: number; + }): Promise => { + const pendingTransaction = await safeWait(options); + if (pendingTransaction.status === 'rejected') { + throw Error( + `Transaction failed with errors:\n${pendingTransaction.errors.join( + '\n' + )}` + ); + } + return pendingTransaction; + }; + + const safeWait = async (options?: { + maxAttempts?: number; + interval?: number; + }): Promise => { + if (status === 'rejected') { + return createRejectedTransaction( + pendingTransaction, + pendingTransaction.errors + ); + } + return await poll(options?.maxAttempts, options?.interval); + }; + + return { + ...pendingTransaction, + wait, + safeWait, + }; + }, + async transaction(sender: FeePayerSpec, f: () => Promise) { + // TODO we run the transcation twice to be able to fetch data in between + let tx = await createTransaction(sender, f, 0, { + fetchMode: 'test', + isFinalRunOutsideCircuit: false, + }); + await Fetch.fetchMissingData(minaGraphqlEndpoint, archiveEndpoint); + let hasProofs = tx.transaction.accountUpdates.some( + Authorization.hasLazyProof + ); + return await createTransaction(sender, f, 1, { + fetchMode: 'cached', + isFinalRunOutsideCircuit: !hasProofs, + }); + }, + async fetchEvents( + publicKey: PublicKey, + tokenId: Field = TokenId.default, + filterOptions: EventActionFilterOptions = {} + ) { + let pubKey = publicKey.toBase58(); + let token = TokenId.toBase58(tokenId); + + return Fetch.fetchEvents( + { publicKey: pubKey, tokenId: token }, + archiveEndpoint, + filterOptions + ); + }, + async fetchActions( + publicKey: PublicKey, + actionStates?: ActionStates, + tokenId: Field = TokenId.default + ) { + let pubKey = publicKey.toBase58(); + let token = TokenId.toBase58(tokenId); + let { fromActionState, endActionState } = actionStates ?? {}; + let fromActionStateBase58 = fromActionState + ? fromActionState.toString() + : undefined; + let endActionStateBase58 = endActionState + ? endActionState.toString() + : undefined; + + return Fetch.fetchActions( + { + publicKey: pubKey, + actionStates: { + fromActionState: fromActionStateBase58, + endActionState: endActionStateBase58, + }, + tokenId: token, + }, + archiveEndpoint + ); + }, + getActions( + publicKey: PublicKey, + actionStates?: ActionStates, + tokenId: Field = TokenId.default + ) { + if (currentTransaction()?.fetchMode === 'test') { + Fetch.markActionsToBeFetched( + publicKey, + tokenId, + archiveEndpoint, + actionStates + ); + let actions = Fetch.getCachedActions(publicKey, tokenId); + return actions ?? []; + } + if ( + !currentTransaction.has() || + currentTransaction.get().fetchMode === 'cached' + ) { + let actions = Fetch.getCachedActions(publicKey, tokenId); + if (actions !== undefined) return actions; + } + throw Error( + `getActions: Could not find actions for the public key ${publicKey}` + ); + }, + proofsEnabled: true, + }; +} + +/** + * Returns the public key of the current transaction's sender account. + * + * Throws an error if not inside a transaction, or the sender wasn't passed in. + */ +function sender() { + let tx = currentTransaction(); + if (tx === undefined) + throw Error( + `The sender is not available outside a transaction. Make sure you only use it within \`Mina.transaction\` blocks or smart contract methods.` + ); + let sender = currentTransaction()?.sender; + if (sender === undefined) + throw Error( + `The sender is not available, because the transaction block was created without the optional \`sender\` argument. +Here's an example for how to pass in the sender and make it available: + +Mina.transaction(sender, // <-- pass in sender's public key here +() => { + // methods can use this.sender +}); +` + ); + return sender; +} + +function dummyAccount(pubkey?: PublicKey): Account { + let dummy = Types.Account.empty(); + if (pubkey) dummy.publicKey = pubkey; + return dummy; +} + +async function waitForFunding(address: string): Promise { + let attempts = 0; + let maxAttempts = 30; + let interval = 30000; + const executePoll = async ( + resolve: () => void, + reject: (err: Error) => void | Error + ) => { + let { account } = await Fetch.fetchAccount({ publicKey: address }); + attempts++; + if (account) { + return resolve(); + } else if (maxAttempts && attempts === maxAttempts) { + return reject(new Error(`Exceeded max attempts`)); + } else { + setTimeout(executePoll, interval, resolve, reject); + } + }; + return new Promise(executePoll); +} + +/** + * Requests the [testnet faucet](https://faucet.minaprotocol.com/api/v1/faucet) to fund a public key. + */ +async function faucet(pub: PublicKey, network: string = 'berkeley-qanet') { + let address = pub.toBase58(); + let response = await fetch('https://faucet.minaprotocol.com/api/v1/faucet', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + network, + address: address, + }), + }); + response = await response.json(); + if (response.status.toString() !== 'success') { + throw new Error( + `Error funding account ${address}, got response status: ${response.status}, text: ${response.statusText}` + ); + } + await waitForFunding(address); +} + +function genesisToNetworkConstants( + genesisConstants: Fetch.GenesisConstants +): NetworkConstants { + return { + genesisTimestamp: UInt64.from( + Date.parse(genesisConstants.genesisTimestamp) + ), + slotTime: UInt64.from(genesisConstants.slotDuration), + accountCreationFee: UInt64.from(genesisConstants.accountCreationFee), + }; +} diff --git a/src/lib/mina.unit-test.ts b/src/lib/mina/mina.unit-test.ts similarity index 97% rename from src/lib/mina.unit-test.ts rename to src/lib/mina/mina.unit-test.ts index 682b453de3..d55d136909 100644 --- a/src/lib/mina.unit-test.ts +++ b/src/lib/mina/mina.unit-test.ts @@ -1,6 +1,5 @@ import { filterGroups } from './mina.js'; import { expect } from 'expect'; -import { shutdown } from '../index.js'; let S = { isProved: false, isSigned: true }; let N = { isProved: false, isSigned: false }; @@ -101,4 +100,3 @@ expect(filterGroups([S, S])).toEqual({ signedPair: 1, signedSingle: 0, }); -shutdown(); diff --git a/src/lib/precondition.test.ts b/src/lib/mina/precondition.test.ts similarity index 76% rename from src/lib/precondition.test.ts rename to src/lib/mina/precondition.test.ts index 7b6c5b4d1a..34b441629c 100644 --- a/src/lib/precondition.test.ts +++ b/src/lib/mina/precondition.test.ts @@ -1,6 +1,4 @@ import { - shutdown, - isReady, UInt64, UInt32, SmartContract, @@ -11,10 +9,10 @@ import { PublicKey, Bool, Field, -} from 'snarkyjs'; +} from 'o1js'; class MyContract extends SmartContract { - @method shouldMakeCompileThrow() { + @method async shouldMakeCompileThrow() { this.network.blockchainLength.get(); } } @@ -27,7 +25,6 @@ let feePayerKey: PrivateKey; beforeAll(async () => { // set up local blockchain, create zkapp keys, deploy the contract - await isReady; let Local = Mina.LocalBlockchain({ proofsEnabled: false }); Mina.setActiveInstance(Local); feePayerKey = Local.testAccounts[0].privateKey; @@ -37,19 +34,18 @@ beforeAll(async () => { zkappAddress = zkappKey.toPublicKey(); zkapp = new MyContract(zkappAddress); - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); + await zkapp.deploy(); }); tx.sign([feePayerKey, zkappKey]).send(); }); -afterAll(() => setTimeout(shutdown, 0)); describe('preconditions', () => { it('get without constraint should throw', async () => { for (let precondition of implemented) { await expect( - Mina.transaction(feePayer, () => { + Mina.transaction(feePayer, async () => { precondition().get(); AccountUpdate.attachToTransaction(zkapp.self); }) @@ -61,13 +57,13 @@ describe('preconditions', () => { await expect(() => MyContract.compile()).rejects.toThrow('precondition'); }); - it('get + assertEquals should not throw', async () => { + it('get + requireEquals should not throw', async () => { let nonce = zkapp.account.nonce.get(); - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { zkapp.requireSignature(); for (let precondition of implemented) { let p = precondition().get(); - precondition().assertEquals(p as any); + precondition().requireEquals(p as any); } AccountUpdate.attachToTransaction(zkapp.self); }); @@ -76,24 +72,24 @@ describe('preconditions', () => { expect(zkapp.account.nonce.get()).toEqual(nonce.add(1)); }); - it('get + assertEquals should throw for unimplemented fields', async () => { + it('get + requireEquals should throw for unimplemented fields', async () => { for (let precondition of unimplemented) { await expect( - Mina.transaction(feePayer, () => { + Mina.transaction(feePayer, async () => { let p = precondition(); - p.assertEquals(p.get() as any); + p.requireEquals(p.get() as any); AccountUpdate.attachToTransaction(zkapp.self); }) ).rejects.toThrow(/not implemented/); } }); - it('get + assertBetween should not throw', async () => { + it('get + requireBetween should not throw', async () => { let nonce = zkapp.account.nonce.get(); - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { for (let precondition of implementedWithRange) { let p: any = precondition().get(); - precondition().assertBetween(p.constructor.zero, p); + precondition().requireBetween(p.constructor.zero, p); } zkapp.requireSignature(); AccountUpdate.attachToTransaction(zkapp.self); @@ -103,10 +99,10 @@ describe('preconditions', () => { expect(zkapp.account.nonce.get()).toEqual(nonce.add(1)); }); - it('satisfied currentSlot.assertBetween should not throw', async () => { + it('satisfied currentSlot.requireBetween should not throw', async () => { let nonce = zkapp.account.nonce.get(); - let tx = await Mina.transaction(feePayer, () => { - zkapp.currentSlot.assertBetween( + let tx = await Mina.transaction(feePayer, async () => { + zkapp.currentSlot.requireBetween( UInt32.from(0), UInt32.from(UInt32.MAXINT()) ); @@ -117,12 +113,12 @@ describe('preconditions', () => { expect(zkapp.account.nonce.get()).toEqual(nonce.add(1)); }); - it('get + assertNothing should not throw', async () => { + it('get + requireNothing should not throw', async () => { let nonce = zkapp.account.nonce.get(); - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { for (let precondition of implemented) { precondition().get(); - precondition().assertNothing(); + precondition().requireNothing(); } zkapp.requireSignature(); AccountUpdate.attachToTransaction(zkapp.self); @@ -135,7 +131,7 @@ describe('preconditions', () => { it('get + manual precondition should not throw', async () => { // we only test this for a couple of preconditions let nonce = zkapp.account.nonce.get(); - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { zkapp.account.balance.get(); zkapp.self.body.preconditions.account.balance.isSome = Bool(true); zkapp.self.body.preconditions.account.balance.value.upper = @@ -159,12 +155,12 @@ describe('preconditions', () => { expect(zkapp.account.nonce.get()).toEqual(nonce.add(1)); }); - it('unsatisfied assertEquals should be rejected (numbers)', async () => { + it('unsatisfied requireEquals should be rejected (numbers)', async () => { for (let precondition of implementedNumber) { await expect(async () => { - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { let p = precondition().get(); - precondition().assertEquals(p.add(1) as any); + precondition().requireEquals(p.add(1) as any); AccountUpdate.attachToTransaction(zkapp.self); }); await tx.sign([feePayerKey]).send(); @@ -172,11 +168,11 @@ describe('preconditions', () => { } }); - it('unsatisfied assertEquals should be rejected (booleans)', async () => { + it('unsatisfied requireEquals should be rejected (booleans)', async () => { for (let precondition of implementedBool) { - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { let p = precondition().get(); - precondition().assertEquals(p.not()); + precondition().requireEquals(p.not()); AccountUpdate.attachToTransaction(zkapp.self); }); await expect(tx.sign([feePayerKey]).send()).rejects.toThrow( @@ -185,20 +181,20 @@ describe('preconditions', () => { } }); - it('unsatisfied assertEquals should be rejected (public key)', async () => { + it('unsatisfied requireEquals should be rejected (public key)', async () => { let publicKey = PublicKey.from({ x: Field(-1), isOdd: Bool(false) }); - let tx = await Mina.transaction(feePayer, () => { - zkapp.account.delegate.assertEquals(publicKey); + let tx = await Mina.transaction(feePayer, async () => { + zkapp.account.delegate.requireEquals(publicKey); AccountUpdate.attachToTransaction(zkapp.self); }); await expect(tx.sign([feePayerKey]).send()).rejects.toThrow(/unsatisfied/); }); - it('unsatisfied assertBetween should be rejected', async () => { + it('unsatisfied requireBetween should be rejected', async () => { for (let precondition of implementedWithRange) { - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { let p: any = precondition().get(); - precondition().assertBetween(p.add(20), p.add(30)); + precondition().requireBetween(p.add(20), p.add(30)); AccountUpdate.attachToTransaction(zkapp.self); }); await expect(tx.sign([feePayerKey]).send()).rejects.toThrow( @@ -207,9 +203,9 @@ describe('preconditions', () => { } }); - it('unsatisfied currentSlot.assertBetween should be rejected', async () => { - let tx = await Mina.transaction(feePayer, () => { - zkapp.currentSlot.assertBetween(UInt32.from(20), UInt32.from(30)); + it('unsatisfied currentSlot.requireBetween should be rejected', async () => { + let tx = await Mina.transaction(feePayer, async () => { + zkapp.currentSlot.requireBetween(UInt32.from(20), UInt32.from(30)); AccountUpdate.attachToTransaction(zkapp.self); }); await expect(tx.sign([feePayerKey]).send()).rejects.toThrow(/unsatisfied/); @@ -217,10 +213,10 @@ describe('preconditions', () => { // TODO: is this a gotcha that should be addressed? // the test below fails, so it seems that nonce is applied successfully with a WRONG precondition.. - // however, this is just because `zkapp.sign()` overwrites the nonce precondition with one that is satisfied + // however, this is just because `zkapp.requireSignature()` overwrites the nonce precondition with one that is satisfied it.skip('unsatisfied nonce precondition should be rejected', async () => { - let tx = await Mina.transaction(feePayer, () => { - zkapp.account.nonce.assertEquals(UInt32.from(1e8)); + let tx = await Mina.transaction(feePayer, async () => { + zkapp.account.nonce.requireEquals(UInt32.from(1e8)); zkapp.requireSignature(); AccountUpdate.attachToTransaction(zkapp.self); }); diff --git a/src/lib/precondition.ts b/src/lib/mina/precondition.ts similarity index 72% rename from src/lib/precondition.ts rename to src/lib/mina/precondition.ts index 53e044f6ed..3d3942b66f 100644 --- a/src/lib/precondition.ts +++ b/src/lib/mina/precondition.ts @@ -1,14 +1,25 @@ -import { Bool, Field } from './core.js'; -import { circuitValueEquals } from './circuit_value.js'; -import { Provable } from './provable.js'; -import * as Mina from './mina.js'; -import { Actions, AccountUpdate, Preconditions } from './account_update.js'; -import { Int64, UInt32, UInt64 } from './int.js'; -import { Layout } from '../bindings/mina-transaction/gen/transaction.js'; -import { jsLayout } from '../bindings/mina-transaction/gen/js-layout.js'; -import { emptyReceiptChainHash, TokenSymbol } from './hash.js'; -import { PublicKey } from './signature.js'; -import { ZkappUri } from '../bindings/mina-transaction/transaction-leaves.js'; +import { Bool, Field } from '../provable/wrapped.js'; +import { + circuitValueEquals, + cloneCircuitValue, +} from '../provable/types/struct.js'; +import { Provable } from '../provable/provable.js'; +import { activeInstance as Mina } from './mina-instance.js'; +import type { AccountUpdate } from './account-update.js'; +import { Int64, UInt32, UInt64 } from '../provable/int.js'; +import { Layout } from '../../bindings/mina-transaction/gen/transaction.js'; +import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; +import { + emptyReceiptChainHash, + TokenSymbol, +} from '../provable/crypto/poseidon.js'; +import { PublicKey } from '../provable/crypto/signature.js'; +import { + Actions, + ZkappUri, +} from '../../bindings/mina-transaction/transaction-leaves.js'; +import type { Types } from '../../bindings/mina-transaction/types.js'; +import { ZkappStateLength } from './mina-instance.js'; export { preconditions, @@ -20,6 +31,112 @@ export { AccountValue, NetworkValue, getAccountPreconditions, + Preconditions, + OrIgnore, + ClosedInterval, +}; + +type AccountUpdateBody = Types.AccountUpdate['body']; + +/** + * Preconditions for the network and accounts + */ +type Preconditions = AccountUpdateBody['preconditions']; + +/** + * Either check a value or ignore it. + * + * Used within [[ AccountPredicate ]]s and [[ ProtocolStatePredicate ]]s. + */ +type OrIgnore = { isSome: Bool; value: T }; + +/** + * An interval representing all the values between `lower` and `upper` inclusive + * of both the `lower` and `upper` values. + * + * @typeParam A something with an ordering where one can quantify a lower and + * upper bound. + */ +type ClosedInterval = { lower: T; upper: T }; + +type NetworkPrecondition = Preconditions['network']; +let NetworkPrecondition = { + ignoreAll(): NetworkPrecondition { + let stakingEpochData = { + ledger: { hash: ignore(Field(0)), totalCurrency: ignore(uint64()) }, + seed: ignore(Field(0)), + startCheckpoint: ignore(Field(0)), + lockCheckpoint: ignore(Field(0)), + epochLength: ignore(uint32()), + }; + let nextEpochData = cloneCircuitValue(stakingEpochData); + return { + snarkedLedgerHash: ignore(Field(0)), + blockchainLength: ignore(uint32()), + minWindowDensity: ignore(uint32()), + totalCurrency: ignore(uint64()), + globalSlotSinceGenesis: ignore(uint32()), + stakingEpochData, + nextEpochData, + }; + }, +}; + +/** + * Ignores a `dummy` + * + * @param dummy The value to ignore + * @returns Always an ignored value regardless of the input. + */ +function ignore(dummy: T): OrIgnore { + return { isSome: Bool(false), value: dummy }; +} + +/** + * Ranges between all uint32 values + */ +const uint32 = () => ({ lower: UInt32.from(0), upper: UInt32.MAXINT() }); + +/** + * Ranges between all uint64 values + */ +const uint64 = () => ({ lower: UInt64.from(0), upper: UInt64.MAXINT() }); + +type AccountPrecondition = Preconditions['account']; +const AccountPrecondition = { + ignoreAll(): AccountPrecondition { + let appState: Array> = []; + for (let i = 0; i < ZkappStateLength; ++i) { + appState.push(ignore(Field(0))); + } + return { + balance: ignore(uint64()), + nonce: ignore(uint32()), + receiptChainHash: ignore(Field(0)), + delegate: ignore(PublicKey.empty()), + state: appState, + actionState: ignore(Actions.emptyActionState()), + provedState: ignore(Bool(false)), + isNew: ignore(Bool(false)), + }; + }, +}; + +type GlobalSlotPrecondition = Preconditions['validWhile']; +const GlobalSlotPrecondition = { + ignoreAll(): GlobalSlotPrecondition { + return ignore(uint32()); + }, +}; + +const Preconditions = { + ignoreAll(): Preconditions { + return { + account: AccountPrecondition.ignoreAll(), + network: NetworkPrecondition.ignoreAll(), + validWhile: GlobalSlotPrecondition.ignoreAll(), + }; + }, }; function preconditions(accountUpdate: AccountUpdate, isSelf: boolean) { @@ -49,26 +166,28 @@ function Network(accountUpdate: AccountUpdate): Network { let slot = network.globalSlotSinceGenesis.get(); return globalSlotToTimestamp(slot); }, - getAndAssertEquals() { - let slot = network.globalSlotSinceGenesis.getAndAssertEquals(); + getAndRequireEquals() { + let slot = network.globalSlotSinceGenesis.getAndRequireEquals(); return globalSlotToTimestamp(slot); }, - assertEquals(value: UInt64) { - let { genesisTimestamp, slotTime } = - Mina.activeInstance.getNetworkConstants(); + requireEquals(value: UInt64) { + let { genesisTimestamp, slotTime } = Mina.getNetworkConstants(); let slot = timestampToGlobalSlot( value, `Timestamp precondition unsatisfied: the timestamp can only equal numbers of the form ${genesisTimestamp} + k*${slotTime},\n` + `i.e., the genesis timestamp plus an integer number of slots.` ); - return network.globalSlotSinceGenesis.assertEquals(slot); + return network.globalSlotSinceGenesis.requireEquals(slot); }, - assertBetween(lower: UInt64, upper: UInt64) { + requireBetween(lower: UInt64, upper: UInt64) { let [slotLower, slotUpper] = timestampToGlobalSlotRange(lower, upper); - return network.globalSlotSinceGenesis.assertBetween(slotLower, slotUpper); + return network.globalSlotSinceGenesis.requireBetween( + slotLower, + slotUpper + ); }, - assertNothing() { - return network.globalSlotSinceGenesis.assertNothing(); + requireNothing() { + return network.globalSlotSinceGenesis.requireNothing(); }, }; return { ...network, timestamp }; @@ -118,7 +237,7 @@ function updateSubclass( function CurrentSlot(accountUpdate: AccountUpdate): CurrentSlot { let context = getPreconditionContextExn(accountUpdate); return { - assertBetween(lower: UInt32, upper: UInt32) { + requireBetween(lower: UInt32, upper: UInt32) { context.constrained.add('validWhile'); let property: RangeCondition = accountUpdate.body.preconditions.validWhile; @@ -193,7 +312,7 @@ function preconditionSubClassWithRange< ) { return { ...preconditionSubclass(accountUpdate, longKey, fieldType as any, context), - assertBetween(lower: any, upper: any) { + requireBetween(lower: any, upper: any) { context.constrained.add(longKey); let property: RangeCondition = getPath( accountUpdate.body.preconditions, @@ -232,12 +351,12 @@ function preconditionSubclass< fieldType )) as U; }, - getAndAssertEquals() { + getAndRequireEquals() { let value = obj.get(); - obj.assertEquals(value); + obj.requireEquals(value); return value; }, - assertEquals(value: U) { + requireEquals(value: U) { context.constrained.add(longKey); let property = getPath( accountUpdate.body.preconditions, @@ -255,7 +374,14 @@ function preconditionSubclass< setPath(accountUpdate.body.preconditions, longKey, value); } }, - assertNothing() { + requireNothing() { + let property = getPath( + accountUpdate.body.preconditions, + longKey + ) as AnyCondition; + if ('isSome' in property) { + property.isSome = Bool(false); + } context.constrained.add(longKey); }, }; @@ -288,13 +414,11 @@ function getVariable( } function globalSlotToTimestamp(slot: UInt32) { - let { genesisTimestamp, slotTime } = - Mina.activeInstance.getNetworkConstants(); + let { genesisTimestamp, slotTime } = Mina.getNetworkConstants(); return UInt64.from(slot).mul(slotTime).add(genesisTimestamp); } function timestampToGlobalSlot(timestamp: UInt64, message: string) { - let { genesisTimestamp, slotTime } = - Mina.activeInstance.getNetworkConstants(); + let { genesisTimestamp, slotTime } = Mina.getNetworkConstants(); let { quotient: slot, rest } = timestamp .sub(genesisTimestamp) .divMod(slotTime); @@ -309,8 +433,7 @@ function timestampToGlobalSlotRange( // we need `slotLower <= current slot <= slotUpper` to imply `tsLower <= current timestamp <= tsUpper` // so we have to make the range smaller -- round up `tsLower` and round down `tsUpper` // also, we should clamp to the UInt32 max range [0, 2**32-1] - let { genesisTimestamp, slotTime } = - Mina.activeInstance.getNetworkConstants(); + let { genesisTimestamp, slotTime } = Mina.getNetworkConstants(); let tsLowerInt = Int64.from(tsLower) .sub(genesisTimestamp) .add(slotTime) @@ -386,7 +509,7 @@ function assertPreconditionInvariants(accountUpdate: AccountUpdate) { let self = context.isSelf ? 'this' : 'accountUpdate'; let dummyPreconditions = Preconditions.ignoreAll(); for (let preconditionPath of context.read) { - // check if every precondition that was read was also contrained + // check if every precondition that was read was also constrained if (context.constrained.has(preconditionPath)) continue; // check if the precondition was modified manually, which is also a valid way of avoiding an error @@ -398,14 +521,14 @@ function assertPreconditionInvariants(accountUpdate: AccountUpdate) { if (!circuitValueEquals(precondition, dummy)) continue; // we accessed a precondition field but not constrained it explicitly - throw an error - let hasAssertBetween = isRangeCondition(precondition); + let hasRequireBetween = isRangeCondition(precondition); let shortPath = preconditionPath.split('.').pop(); let errorMessage = `You used \`${self}.${preconditionPath}.get()\` without adding a precondition that links it to the actual ${shortPath}. Consider adding this line to your code: -${self}.${preconditionPath}.assertEquals(${self}.${preconditionPath}.get());${ - hasAssertBetween +${self}.${preconditionPath}.requireEquals(${self}.${preconditionPath}.get());${ + hasRequireBetween ? ` -You can also add more flexible preconditions with \`${self}.${preconditionPath}.assertBetween(...)\`.` +You can also add more flexible preconditions with \`${self}.${preconditionPath}.requireBetween(...)\`.` : '' }`; throw Error(errorMessage); @@ -422,7 +545,6 @@ const preconditionContexts = new WeakMap(); // exported types -type NetworkPrecondition = Preconditions['network']; type NetworkValue = PreconditionBaseTypes; type RawNetwork = PreconditionClassType; type Network = RawNetwork & { @@ -431,13 +553,13 @@ type Network = RawNetwork & { // TODO: should we add account.state? // then can just use circuitArray(Field, 8) as the type -type AccountPrecondition = Omit; -type AccountValue = PreconditionBaseTypes; -type Account = PreconditionClassType & Update; +type AccountPreconditionNoState = Omit; +type AccountValue = PreconditionBaseTypes; +type Account = PreconditionClassType & Update; type CurrentSlotPrecondition = Preconditions['validWhile']; type CurrentSlot = { - assertBetween(lower: UInt32, upper: UInt32): void; + requireBetween(lower: UInt32, upper: UInt32): void; }; type PreconditionBaseTypes = { @@ -452,12 +574,12 @@ type PreconditionBaseTypes = { type PreconditionSubclassType = { get(): U; - getAndAssertEquals(): U; - assertEquals(value: U): void; - assertNothing(): void; + getAndRequireEquals(): U; + requireEquals(value: U): void; + requireNothing(): void; }; type PreconditionSubclassRangeType = PreconditionSubclassType & { - assertBetween(lower: U, upper: U): void; + requireBetween(lower: U, upper: U): void; }; type PreconditionClassType = { @@ -502,7 +624,7 @@ type PreconditionFlatEntry = T extends RangeCondition type FlatPreconditionValue = { [S in PreconditionFlatEntry as `network.${S[0]}`]: S[2]; } & { - [S in PreconditionFlatEntry as `account.${S[0]}`]: S[2]; + [S in PreconditionFlatEntry as `account.${S[0]}`]: S[2]; } & { validWhile: PreconditionFlatEntry[2] }; type LongKey = keyof FlatPreconditionValue; diff --git a/src/lib/mina/smart-contract-base.ts b/src/lib/mina/smart-contract-base.ts new file mode 100644 index 0000000000..0c0db7c805 --- /dev/null +++ b/src/lib/mina/smart-contract-base.ts @@ -0,0 +1,13 @@ +/** + * This file exists to avoid an import cycle between code that just needs access + * to a smart contract base class, and the code that implements `SmartContract`. + */ +import type { SmartContract } from './zkapp.js'; + +export { isSmartContract, SmartContractBase }; + +class SmartContractBase {} + +function isSmartContract(object: unknown): object is SmartContract { + return object instanceof SmartContractBase; +} diff --git a/src/lib/mina/smart-contract-context.ts b/src/lib/mina/smart-contract-context.ts new file mode 100644 index 0000000000..2920c26d42 --- /dev/null +++ b/src/lib/mina/smart-contract-context.ts @@ -0,0 +1,25 @@ +import type { SmartContract } from './zkapp.js'; +import type { AccountUpdate, AccountUpdateLayout } from './account-update.js'; +import { Context } from '../util/global-context.js'; +import { currentTransaction } from './transaction-context.js'; + +export { smartContractContext, SmartContractContext, accountUpdateLayout }; + +type SmartContractContext = { + this: SmartContract; + selfUpdate: AccountUpdate; + selfLayout: AccountUpdateLayout; +}; +let smartContractContext = Context.create({ + default: null, +}); + +function accountUpdateLayout() { + // in a smart contract, return the layout currently created in the contract call + let layout = smartContractContext.get()?.selfLayout; + + // if not in a smart contract but in a transaction, return the layout of the transaction + layout ??= currentTransaction()?.layout; + + return layout; +} diff --git a/src/lib/state.ts b/src/lib/mina/state.ts similarity index 85% rename from src/lib/state.ts rename to src/lib/mina/state.ts index 1c4c464e49..3f16428782 100644 --- a/src/lib/state.ts +++ b/src/lib/mina/state.ts @@ -1,13 +1,13 @@ -import { ProvablePure } from '../snarky.js'; -import { FlexibleProvablePure } from './circuit_value.js'; -import { AccountUpdate, TokenId } from './account_update.js'; -import { PublicKey } from './signature.js'; +import { FlexibleProvablePure } from '../provable/types/struct.js'; +import { AccountUpdate, TokenId } from './account-update.js'; +import { PublicKey } from '../provable/crypto/signature.js'; import * as Mina from './mina.js'; -import { fetchAccount } from './fetch.js'; +import { fetchAccount, networkConfig } from './fetch.js'; import { SmartContract } from './zkapp.js'; -import { Account } from './mina/account.js'; -import { Provable } from './provable.js'; -import { Field } from '../lib/core.js'; +import { Account } from './account.js'; +import { Provable } from '../provable/provable.js'; +import { Field } from '../provable/wrapped.js'; +import { ProvablePure } from '../provable/types/provable-intf.js'; // external API export { State, state, declareState }; @@ -22,17 +22,17 @@ type State = { * Get the current on-chain state. * * Caution: If you use this method alone inside a smart contract, it does not prove that your contract uses the current on-chain state. - * To successfully prove that your contract uses the current on-chain state, you must add an additional `.assertEquals()` statement or use `.getAndAssertEquals()`: + * To successfully prove that your contract uses the current on-chain state, you must add an additional `.requireEquals()` statement or use `.getAndRequireEquals()`: * * ```ts * let x = this.x.get(); - * this.x.assertEquals(x); + * this.x.requireEquals(x); * ``` * * OR * * ```ts - * let x = this.x.getAndAssertEquals(); + * let x = this.x.getAndRequireEquals(); * ``` */ get(): A; @@ -40,7 +40,7 @@ type State = { * Get the current on-chain state and prove it really has to equal the on-chain state, * by adding a precondition which the verifying Mina node will check before accepting this transaction. */ - getAndAssertEquals(): A; + getAndRequireEquals(): A; /** * Set the on-chain state to a new value. */ @@ -53,11 +53,11 @@ type State = { * Prove that the on-chain state has to equal the given state, * by adding a precondition which the verifying Mina node will check before accepting this transaction. */ - assertEquals(a: A): void; + requireEquals(a: A): void; /** * **DANGER ZONE**: Override the error message that warns you when you use `.get()` without adding a precondition. */ - assertNothing(): void; + requireNothing(): void; /** * Get the state from the raw list of field elements on a zkApp account, for example: * @@ -203,10 +203,10 @@ function createState(): InternalStateType { }); }, - assertEquals(state: T) { + requireEquals(state: T) { if (this._contract === undefined) throw Error( - 'assertEquals can only be called when the State is assigned to a SmartContract @state.' + 'requireEquals can only be called when the State is assigned to a SmartContract @state.' ); let layout = getLayoutPosition(this._contract); let stateAsFields = this._contract.stateType.toFields(state); @@ -220,10 +220,10 @@ function createState(): InternalStateType { this._contract.wasConstrained = true; }, - assertNothing() { + requireNothing() { if (this._contract === undefined) throw Error( - 'assertNothing can only be called when the State is assigned to a SmartContract @state.' + 'requireNothing can only be called when the State is assigned to a SmartContract @state.' ); this._contract.wasConstrained = true; }, @@ -256,18 +256,23 @@ function createState(): InternalStateType { contract.instance.address, contract.instance.self.body.tokenId ); - } catch (err) { + } catch (err: any) { // TODO: there should also be a reasonable error here if (inProver_) { throw err; } - throw Error( + let message = `${contract.key}.get() failed, either:\n` + - `1. We can't find this zkapp account in the ledger\n` + - `2. Because the zkapp account was not found in the cache. ` + - `Try calling \`await fetchAccount(zkappAddress)\` first.\n` + - `If none of these are the case, then please reach out on Discord at #zkapp-developers and/or open an issue to tell us!` - ); + `1. We can't find this zkapp account in the ledger\n` + + `2. Because the zkapp account was not found in the cache. ` + + `Try calling \`await fetchAccount(zkappAddress)\` first.\n` + + `If none of these are the case, then please reach out on Discord at #zkapp-developers and/or open an issue to tell us!`; + if (err.message) { + err.message = message + `\n\n${err.message}`; + throw err; + } else { + throw Error(message); + } } if (account.zkapp?.appState === undefined) { // if the account is not a zkapp account, let the default state be all zeroes @@ -289,9 +294,9 @@ function createState(): InternalStateType { return state; }, - getAndAssertEquals() { + getAndRequireEquals() { let state = this.get(); - this.assertEquals(state); + this.requireEquals(state); return state; }, @@ -300,17 +305,20 @@ function createState(): InternalStateType { throw Error( 'fetch can only be called when the State is assigned to a SmartContract @state.' ); - if (Mina.currentTransaction.has()) - throw Error( - 'fetch is not intended to be called inside a transaction block.' - ); + let layout = getLayoutPosition(this._contract); let address: PublicKey = this._contract.instance.address; - let { account } = await fetchAccount({ - publicKey: address, - tokenId: TokenId.toBase58(TokenId.default), - }); + let account: Account | undefined; + if (networkConfig.minaEndpoint === '') { + account = Mina.getAccount(address, TokenId.default); + } else { + ({ account } = await fetchAccount({ + publicKey: address, + tokenId: TokenId.toBase58(TokenId.default), + })); + } if (account === undefined) return undefined; + let stateAsFields: Field[]; if (account.zkapp?.appState === undefined) { stateAsFields = Array(layout.length).fill(Field(0)); @@ -380,12 +388,12 @@ const reservedPropNames = new Set(['_methods', '_']); function assertStatePrecondition(sc: SmartContract) { try { for (let [key, context] of getStateContexts(sc)) { - // check if every state that was read was also contrained + // check if every state that was read was also constrained if (!context?.wasRead || context.wasConstrained) continue; // we accessed a precondition field but not constrained it explicitly - throw an error let errorMessage = `You used \`this.${key}.get()\` without adding a precondition that links it to the actual on-chain state. Consider adding this line to your code: -this.${key}.assertEquals(this.${key}.get());`; +this.${key}.requireEquals(this.${key}.get());`; throw Error(errorMessage); } } finally { diff --git a/src/lib/token.test.ts b/src/lib/mina/token.test.ts similarity index 75% rename from src/lib/token.test.ts rename to src/lib/mina/token.test.ts index e9fad45873..eafa963a35 100644 --- a/src/lib/token.test.ts +++ b/src/lib/mina/token.test.ts @@ -12,23 +12,33 @@ import { Permissions, VerificationKey, Field, - Experimental, Int64, TokenId, -} from 'snarkyjs'; + TokenContract as TokenContractBase, + AccountUpdateForest, +} from 'o1js'; const tokenSymbol = 'TOKEN'; -class TokenContract extends SmartContract { +// TODO: Refactor to use `TokenContract.approveBase()` + +class TokenContract extends TokenContractBase { SUPPLY = UInt64.from(10n ** 18n); @state(UInt64) totalAmountInCirculation = State(); + async approveBase(_: AccountUpdateForest) { + throw Error('Not used'); + } + /** * This deploy method lets a another token account deploy their zkApp and verification key as a child of this token contract. * This is important since we want the native token id of the deployed zkApp to be the token id of the token contract. */ - @method deployZkapp(address: PublicKey, verificationKey: VerificationKey) { - let tokenId = this.token.id; + @method async deployZkapp( + address: PublicKey, + verificationKey: VerificationKey + ) { + let tokenId = this.deriveTokenId(); let zkapp = AccountUpdate.defaultAccountUpdate(address, tokenId); this.approve(zkapp); zkapp.account.permissions.set(Permissions.default()); @@ -39,13 +49,14 @@ class TokenContract extends SmartContract { init() { super.init(); let address = this.address; - let receiver = this.token.mint({ - address, - amount: this.SUPPLY, - }); - receiver.account.isNew.assertEquals(Bool(true)); - this.balance.subInPlace(Mina.accountCreationFee()); + let receiver = this.internal.mint({ address, amount: this.SUPPLY }); + receiver.account.isNew.requireEquals(Bool(true)); + this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); this.totalAmountInCirculation.set(this.SUPPLY.sub(100_000_000)); + } + + async deploy() { + await super.deploy(); this.account.permissions.set({ ...Permissions.default(), editState: Permissions.proofOrSignature(), @@ -54,72 +65,55 @@ class TokenContract extends SmartContract { }); } - @method mint(receiverAddress: PublicKey, amount: UInt64) { + @method async mint(receiverAddress: PublicKey, amount: UInt64) { let totalAmountInCirculation = this.totalAmountInCirculation.get(); - this.totalAmountInCirculation.assertEquals(totalAmountInCirculation); + this.totalAmountInCirculation.requireEquals(totalAmountInCirculation); let newTotalAmountInCirculation = totalAmountInCirculation.add(amount); newTotalAmountInCirculation.value.assertLessThanOrEqual( this.SUPPLY.value, "Can't mint more than the total supply" ); - this.token.mint({ - address: receiverAddress, - amount, - }); + this.internal.mint({ address: receiverAddress, amount }); this.totalAmountInCirculation.set(newTotalAmountInCirculation); } - @method burn(receiverAddress: PublicKey, amount: UInt64) { - let totalAmountInCirculation = this.totalAmountInCirculation.get(); - this.totalAmountInCirculation.assertEquals(totalAmountInCirculation); + @method async burn(receiverAddress: PublicKey, amount: UInt64) { + let totalAmountInCirculation = + this.totalAmountInCirculation.getAndRequireEquals(); let newTotalAmountInCirculation = totalAmountInCirculation.sub(amount); - totalAmountInCirculation.value.assertGreaterThanOrEqual( - UInt64.from(0).value, - "Can't burn less than 0" - ); - this.token.burn({ - address: receiverAddress, - amount, - }); + this.internal.burn({ address: receiverAddress, amount }); this.totalAmountInCirculation.set(newTotalAmountInCirculation); } - @method approveTransferCallback( + @method async approveTransfer( senderAddress: PublicKey, receiverAddress: PublicKey, amount: UInt64, - callback: Experimental.Callback + senderAccountUpdate: AccountUpdate ) { - let layout = AccountUpdate.Layout.NoChildren; // Allow only 1 accountUpdate with no children - let senderAccountUpdate = this.approve(callback, layout); - let negativeAmount = Int64.fromObject( - senderAccountUpdate.body.balanceChange - ); + this.approve(senderAccountUpdate); + let negativeAmount = senderAccountUpdate.balanceChange; negativeAmount.assertEquals(Int64.from(amount).neg()); - let tokenId = this.token.id; + let tokenId = this.deriveTokenId(); senderAccountUpdate.body.tokenId.assertEquals(tokenId); senderAccountUpdate.body.publicKey.assertEquals(senderAddress); - let receiverAccountUpdate = Experimental.createChildAccountUpdate( - this.self, - receiverAddress, - tokenId - ); + let receiverAccountUpdate = AccountUpdate.create(receiverAddress, tokenId); receiverAccountUpdate.balance.addInPlace(amount); } } class ZkAppB extends SmartContract { - @method approveZkapp(amount: UInt64) { + @method async approveSend(amount: UInt64) { this.balance.subInPlace(amount); } } class ZkAppC extends SmartContract { - @method approveZkapp(amount: UInt64) { + @method async approveSend(amount: UInt64) { this.balance.subInPlace(amount); } - @method approveIncorrectLayout(amount: UInt64) { + @method async approveIncorrectLayout(amount: UInt64) { this.balance.subInPlace(amount); let update = AccountUpdate.defaultAccountUpdate(this.address); this.self.approve(update); @@ -154,7 +148,7 @@ function setupAccounts() { tokenZkappAddress = tokenZkappKey.toPublicKey(); tokenZkapp = new TokenContract(tokenZkappAddress); - tokenId = tokenZkapp.token.id; + tokenId = tokenZkapp.deriveTokenId(); zkAppBKey = Local.testAccounts[1].privateKey; zkAppBAddress = zkAppBKey.toPublicKey(); @@ -168,13 +162,13 @@ function setupAccounts() { async function setupLocal() { setupAccounts(); - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { + await tokenZkapp.deploy(); let feePayerUpdate = AccountUpdate.fundNewAccount(feePayer); feePayerUpdate.send({ - to: tokenZkappAddress, - amount: Mina.accountCreationFee(), + to: tokenZkapp.self, + amount: Mina.getNetworkConstants().accountCreationFee, }); - tokenZkapp.deploy(); }); tx.sign([tokenZkappKey, feePayerKey]); await tx.send(); @@ -185,15 +179,15 @@ async function setupLocalProofs() { zkAppC = new ZkAppC(zkAppCAddress, tokenId); // don't use proofs for the setup, takes too long to do this every time Local.setProofsEnabled(false); - let tx = await Mina.transaction({ sender: feePayer }, () => { + let tx = await Mina.transaction({ sender: feePayer }, async () => { + await tokenZkapp.deploy(); let feePayerUpdate = AccountUpdate.fundNewAccount(feePayer, 3); feePayerUpdate.send({ - to: tokenZkappAddress, - amount: Mina.accountCreationFee(), + to: tokenZkapp.self, + amount: Mina.getNetworkConstants().accountCreationFee, }); - tokenZkapp.deploy(); - tokenZkapp.deployZkapp(zkAppBAddress, ZkAppB._verificationKey!); - tokenZkapp.deployZkapp(zkAppCAddress, ZkAppC._verificationKey!); + await tokenZkapp.deployZkapp(zkAppBAddress, ZkAppB._verificationKey!); + await tokenZkapp.deployZkapp(zkAppCAddress, ZkAppC._verificationKey!); }); await tx.prove(); tx.sign([tokenZkappKey, zkAppBKey, zkAppCKey, feePayerKey]); @@ -233,7 +227,7 @@ describe('Token', () => { test('setting a valid token symbol on a token contract', async () => { await ( - await Mina.transaction({ sender: feePayer }, () => { + await Mina.transaction({ sender: feePayer }, async () => { let tokenZkapp = AccountUpdate.createSigned(tokenZkappAddress); tokenZkapp.account.tokenSymbol.set(tokenSymbol); }) @@ -260,9 +254,9 @@ describe('Token', () => { test('token contract can successfully mint and updates the balances in the ledger (signature)', async () => { await ( - await Mina.transaction({ sender: feePayer }, () => { + await Mina.transaction({ sender: feePayer }, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }) ) @@ -274,9 +268,9 @@ describe('Token', () => { }); test('minting should fail if overflow occurs ', async () => { - await Mina.transaction(feePayer, () => { + await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000_000_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000_000_000)); tokenZkapp.requireSignature(); }).catch((e) => { expect(e).toBeDefined(); @@ -297,17 +291,17 @@ describe('Token', () => { }); test('token contract can successfully burn and updates the balances in the ledger (signature)', async () => { await ( - await Mina.transaction(feePayer, () => { + await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }) ) .sign([feePayerKey, tokenZkappKey]) .send(); await ( - await Mina.transaction(feePayer, () => { - tokenZkapp.burn(zkAppBAddress, UInt64.from(10_000)); + await Mina.transaction(feePayer, async () => { + await tokenZkapp.burn(zkAppBAddress, UInt64.from(10_000)); tokenZkapp.requireSignature(); }) ) @@ -320,17 +314,17 @@ describe('Token', () => { test('throw error if token owner burns more tokens than token account has', async () => { await ( - await Mina.transaction(feePayer, () => { + await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(1_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(1_000)); tokenZkapp.requireSignature(); }) ) .sign([feePayerKey, tokenZkappKey]) .send(); let tx = ( - await Mina.transaction(feePayer, () => { - tokenZkapp.burn(zkAppBAddress, UInt64.from(10_000)); + await Mina.transaction(feePayer, async () => { + await tokenZkapp.burn(zkAppBAddress, UInt64.from(10_000)); tokenZkapp.requireSignature(); }) ).sign([zkAppBKey, feePayerKey, tokenZkappKey]); @@ -343,7 +337,7 @@ describe('Token', () => { token contract can transfer tokens with a signature tested cases: - sends tokens and updates the balance of the receiver - - fails if no account creation fee is payed for the new token account + - fails if no account creation fee is paid for the new token account - fails if we transfer more than the balance amount */ describe('Transfer', () => { @@ -352,16 +346,16 @@ describe('Token', () => { }); test('change the balance of a token account after sending', async () => { - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }); await tx.sign([feePayerKey, tokenZkappKey]).send(); - tx = await Mina.transaction(feePayer, () => { + tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.token.send({ + tokenZkapp.internal.send({ from: zkAppBAddress, to: zkAppCAddress, amount: UInt64.from(10_000), @@ -382,17 +376,17 @@ describe('Token', () => { test('should error creating a token account if no account creation fee is specified', async () => { await ( - await Mina.transaction(feePayer, () => { + await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }) ) .sign([feePayerKey, tokenZkappKey]) .send(); let tx = ( - await Mina.transaction(feePayer, () => { - tokenZkapp.token.send({ + await Mina.transaction(feePayer, async () => { + tokenZkapp.internal.send({ from: zkAppBAddress, to: zkAppCAddress, amount: UInt64.from(10_000), @@ -407,17 +401,17 @@ describe('Token', () => { test('should error if sender sends more tokens than they have', async () => { await ( - await Mina.transaction(feePayer, () => { + await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }) ) .sign([feePayerKey, tokenZkappKey]) .send(); let tx = ( - await Mina.transaction(feePayer, () => { - tokenZkapp.token.send({ + await Mina.transaction(feePayer, async () => { + tokenZkapp.internal.send({ from: zkAppBAddress, to: zkAppCAddress, amount: UInt64.from(100_000), @@ -470,9 +464,9 @@ describe('Token', () => { }); test('token contract can successfully mint and updates the balances in the ledger (proof)', async () => { - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); }); await tx.prove(); tx.sign([tokenZkappKey, feePayerKey]); @@ -495,14 +489,14 @@ describe('Token', () => { - burns and updates the token balance of the receiver */ test('token contract can successfully burn and updates the balances in the ledger (proof)', async () => { - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }); await tx.sign([feePayerKey, tokenZkappKey]).send(); - tx = await Mina.transaction(feePayer, () => { - tokenZkapp.burn(zkAppBAddress, UInt64.from(10_000)); + tx = await Mina.transaction(feePayer, async () => { + await tokenZkapp.burn(zkAppBAddress, UInt64.from(10_000)); }); await tx.prove(); tx.sign([zkAppBKey, feePayerKey]); @@ -527,24 +521,21 @@ describe('Token', () => { }); test('should approve and the balance of a token account after sending', async () => { - let tx = await Mina.transaction(feePayer, () => { - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + let tx = await Mina.transaction(feePayer, async () => { + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }); await tx.prove(); await tx.sign([feePayerKey, tokenZkappKey]).send(); - tx = await Mina.transaction(feePayer, () => { - let approveSendingCallback = Experimental.Callback.create( - zkAppB, - 'approveZkapp', - [UInt64.from(10_000)] - ); - tokenZkapp.approveTransferCallback( + tx = await Mina.transaction(feePayer, async () => { + await zkAppB.approveSend(UInt64.from(10_000)); + + await tokenZkapp.approveTransfer( zkAppBAddress, zkAppCAddress, UInt64.from(10_000), - approveSendingCallback + zkAppB.self ); }); await tx.prove(); @@ -560,8 +551,8 @@ describe('Token', () => { test('should fail to approve with an incorrect layout', async () => { await ( - await Mina.transaction(feePayer, () => { - tokenZkapp.mint(zkAppCAddress, UInt64.from(100_000)); + await Mina.transaction(feePayer, async () => { + await tokenZkapp.mint(zkAppCAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }) ) @@ -569,26 +560,22 @@ describe('Token', () => { .send(); await expect(() => - Mina.transaction(feePayer, () => { - let approveSendingCallback = Experimental.Callback.create( - zkAppC, - 'approveIncorrectLayout', - [UInt64.from(10_000)] - ); - tokenZkapp.approveTransferCallback( + Mina.transaction(feePayer, async () => { + await zkAppC.approveIncorrectLayout(UInt64.from(10_000)); + await tokenZkapp.approveTransfer( zkAppBAddress, zkAppCAddress, UInt64.from(10_000), - approveSendingCallback + zkAppC.self ); }) ).rejects.toThrow(); }); test('should reject tx if user bypasses the token contract by using an empty account update', async () => { - let tx = await Mina.transaction(feePayer, () => { + let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.token.mint({ + tokenZkapp.internal.mint({ address: zkAppBAddress, amount: UInt64.from(100_000), }); diff --git a/src/lib/mina/token/forest-iterator.ts b/src/lib/mina/token/forest-iterator.ts new file mode 100644 index 0000000000..bab2a07ccb --- /dev/null +++ b/src/lib/mina/token/forest-iterator.ts @@ -0,0 +1,143 @@ +import { + AccountUpdate, + AccountUpdateForest, + AccountUpdateTreeBase, + TokenId, +} from '../account-update.js'; +import { Field } from '../../provable/wrapped.js'; +import { Provable } from '../../provable/provable.js'; +import { Struct } from '../../provable/types/struct.js'; +import { assert } from '../../provable/gadgets/common.js'; +import { MerkleListIterator, MerkleList } from '../../provable/merkle-list.js'; + +export { TokenAccountUpdateIterator }; + +const AccountUpdateIterator = + MerkleListIterator.createFromList(AccountUpdateForest); + +class Layer extends Struct({ + forest: AccountUpdateIterator.provable, + mayUseToken: AccountUpdate.MayUseToken.type, +}) {} +const ParentLayers = MerkleList.create(Layer); + +type MayUseToken = AccountUpdate['body']['mayUseToken']; +const MayUseToken = AccountUpdate.MayUseToken; + +/** + * Data structure to represent a forest of account updates that is being iterated over, + * in the context of a token manager contract. + * + * The iteration is done in a depth-first manner. + * + * ```ts + * let forest: AccountUpdateForest = ...; + * let tokenIterator = TokenAccountUpdateIterator.create(forest, tokenId); + * + * // process the first 5 account updates in the tree + * for (let i = 0; i < 5; i++) { + * let { accountUpdate, usesThisToken } = tokenIterator.next(); + * // ... do something with the account update ... + * } + * ``` + * + * **Important**: Since this is specifically used by token manager contracts to process their entire subtree + * of account updates, the iterator skips subtrees that don't inherit token permissions and can therefore definitely not use the token. + * + * So, the assumption is that the consumer of this iterator is only interested in account updates that use the token. + * We still can't avoid processing some account updates that don't use the token, therefore the iterator returns a boolean + * `usesThisToken` alongside each account update. + */ +class TokenAccountUpdateIterator { + currentLayer: Layer; + unfinishedParentLayers: MerkleList; + selfToken: Field; + + constructor( + forest: MerkleListIterator, + mayUseToken: MayUseToken, + selfToken: Field + ) { + this.currentLayer = { forest, mayUseToken }; + this.unfinishedParentLayers = ParentLayers.empty(); + this.selfToken = selfToken; + } + + static create(forest: AccountUpdateForest, selfToken: Field) { + return new TokenAccountUpdateIterator( + AccountUpdateIterator.startIterating(forest), + MayUseToken.ParentsOwnToken, + selfToken + ); + } + + /** + * Make a single step along a tree of account updates. + * + * This function is guaranteed to visit each account update in the tree that uses the token + * exactly once, when called repeatedly. + * + * The method makes a best effort to avoid visiting account updates that are not using the token, + * and in particular, to avoid returning dummy updates. + * However, neither can be ruled out. We're returning { update, usesThisToken: Bool } and let the + * caller handle the irrelevant case where `usesThisToken` is false. + */ + next() { + // get next account update from the current forest (might be a dummy) + let { accountUpdate, children } = this.currentLayer.forest.next(); + let childForest = AccountUpdateIterator.startIterating(children); + let childLayer = { + forest: childForest, + mayUseToken: MayUseToken.InheritFromParent, + }; + + let update = accountUpdate.unhash(); + let usesThisToken = update.tokenId.equals(this.selfToken); + + // check if this account update / it's children can use the token + let canAccessThisToken = Provable.equal( + MayUseToken.type, + update.body.mayUseToken, + this.currentLayer.mayUseToken + ); + let isSelf = TokenId.derive(update.publicKey, update.tokenId).equals( + this.selfToken + ); + + // if we don't have to check the children, ignore the forest by jumping to its end + let skipSubtree = canAccessThisToken.not().or(isSelf); + childForest.jumpToEndIf(skipSubtree); + + // there are three cases for how to proceed: + // 1. if we have to process children, we step down and add the current layer to the stack of unfinished parent layers + // 2. if we don't have to process children, but are not finished with the current layer, we stay in the current layer + // (below, this is the case where the current layer is first pushed to and then popped from the stack of unfinished parent layers) + // 3. if both of the above are false, we step up to the next unfinished parent layer + let currentForest = this.currentLayer.forest; + let currentLayerFinished = currentForest.isAtEnd(); + let childLayerFinished = childForest.isAtEnd(); + + this.unfinishedParentLayers.pushIf( + currentLayerFinished.not(), + this.currentLayer + ); + let currentOrParentLayer = + this.unfinishedParentLayers.popIf(childLayerFinished); + + this.currentLayer = Provable.if( + childLayerFinished, + Layer, + currentOrParentLayer, + childLayer + ); + + return { accountUpdate: update, usesThisToken }; + } + + assertFinished(message?: string) { + assert( + this.currentLayer.forest.isAtEnd(), + message ?? 'TokenAccountUpdateIterator not finished' + ); + } +} diff --git a/src/lib/mina/token/forest-iterator.unit-test.ts b/src/lib/mina/token/forest-iterator.unit-test.ts new file mode 100644 index 0000000000..20d4c38af4 --- /dev/null +++ b/src/lib/mina/token/forest-iterator.unit-test.ts @@ -0,0 +1,224 @@ +import { Random, test } from '../../testing/property.js'; +import { RandomTransaction } from '../../../mina-signer/src/random-transaction.js'; +import { TokenAccountUpdateIterator } from './forest-iterator.js'; +import { + AccountUpdate, + AccountUpdateForest, + TokenId, + hashAccountUpdate, +} from '../account-update.js'; +import { TypesBigint } from '../../../bindings/mina-transaction/types.js'; +import { Pickles } from '../../../snarky.js'; +import { + accountUpdatesToCallForest, + callForestHash, +} from '../../../mina-signer/src/sign-zkapp-command.js'; +import assert from 'assert'; +import { Field, Bool } from '../../provable/wrapped.js'; +import { PublicKey } from '../../provable/crypto/signature.js'; + +// RANDOM NUMBER GENERATORS for account updates + +let [, data, hashMl] = Pickles.dummyVerificationKey(); +let dummyVerificationKey = { data, hash: hashMl[1] }; + +const accountUpdateBigint = Random.map( + RandomTransaction.accountUpdateWithCallDepth, + (a) => { + // fix verification key + if (a.body.update.verificationKey.isSome) + a.body.update.verificationKey.value = dummyVerificationKey; + + // ensure that, by default, all account updates are token-accessible + a.body.mayUseToken = + a.body.callDepth === 0 + ? { parentsOwnToken: 1n, inheritFromParent: 0n } + : { parentsOwnToken: 0n, inheritFromParent: 1n }; + return a; + } +); +const accountUpdate = Random.map(accountUpdateBigint, accountUpdateFromBigint); + +const arrayOf = (x: Random) => + // `reset: true` to start callDepth at 0 for each array + Random.array(x, Random.int(10, 40), { reset: true }); + +const flatAccountUpdatesBigint = arrayOf(accountUpdateBigint); +const flatAccountUpdates = arrayOf(accountUpdate); + +// TESTS + +// correctly hashes a call forest + +test.custom({ timeBudget: 1000 })( + flatAccountUpdatesBigint, + (flatUpdatesBigint) => { + // reference: bigint callforest hash from mina-signer + let forestBigint = accountUpdatesToCallForest(flatUpdatesBigint); + let expectedHash = callForestHash(forestBigint, 'testnet'); + + let flatUpdates = flatUpdatesBigint.map(accountUpdateFromBigint); + let forest = AccountUpdateForest.fromFlatArray(flatUpdates); + forest.hash.assertEquals(expectedHash); + } +); + +// traverses the top level of a call forest in correct order +// i.e., CallForestArray works + +test.custom({ timeBudget: 1000 })(flatAccountUpdates, (flatUpdates) => { + // prepare call forest from flat account updates + let forest = AccountUpdateForest.fromFlatArray(flatUpdates).startIterating(); + let updates = flatUpdates.filter((u) => u.body.callDepth === 0); + + // step through top-level by calling forest.next() repeatedly + let n = updates.length; + for (let i = 0; i < n; i++) { + let expected = updates[i]; + let actual = forest.next().accountUpdate.unhash(); + assertEqual(actual, expected); + } + + // doing next() again should return a dummy + let actual = forest.next().accountUpdate.unhash(); + assertEqual(actual, AccountUpdate.dummy()); +}); + +// traverses a call forest in correct depth-first order, +// when no subtrees are skipped + +test.custom({ timeBudget: 5000 })(flatAccountUpdates, (flatUpdates) => { + // with default token id, no subtrees will be skipped + let tokenId = TokenId.default; + + // prepare forest iterator from flat account updates + let forest = AccountUpdateForest.fromFlatArray(flatUpdates); + let forestIterator = TokenAccountUpdateIterator.create(forest, tokenId); + + // step through forest iterator and compare against expected updates + let expectedUpdates = flatUpdates; + + let n = flatUpdates.length; + for (let i = 0; i < n; i++) { + let expected = expectedUpdates[i]; + let actual = forestIterator.next().accountUpdate; + assertEqual(actual, expected); + } + + // doing next() again should return a dummy + let actual = forestIterator.next().accountUpdate; + assertEqual(actual, AccountUpdate.dummy()); +}); + +// correctly skips subtrees for various reasons + +// in this test, we make all updates inaccessible by setting the top level to `no` or `inherit`, or to the token owner +// this means we wouldn't need to traverse any update in the whole tree +// but we only notice inaccessibility when we have already traversed the inaccessible update +// so, the result should be that we traverse the top level and nothing else +test.custom({ timeBudget: 5000 })( + flatAccountUpdates, + Random.publicKey, + (flatUpdates, publicKey) => { + // create token owner and derive token id + let tokenOwner = PublicKey.fromObject({ + x: Field.from(publicKey.x), + isOdd: Bool(!!publicKey.isOdd), + }); + let tokenId = TokenId.derive(tokenOwner); + + // make all top-level updates inaccessible + flatUpdates + .filter((u) => u.body.callDepth === 0) + .forEach((u, i) => { + if (i % 3 === 0) { + u.body.mayUseToken = AccountUpdate.MayUseToken.No; + } else if (i % 3 === 1) { + u.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent; + } else { + u.body.publicKey = tokenOwner; + u.body.tokenId = TokenId.default; + } + }); + + // prepare forest iterator from flat account updates + let forest = AccountUpdateForest.fromFlatArray(flatUpdates); + let forestIterator = TokenAccountUpdateIterator.create(forest, tokenId); + + // step through forest iterator and compare against expected updates + let expectedUpdates = flatUpdates.filter((u) => u.body.callDepth === 0); + + let n = flatUpdates.length; + for (let i = 0; i < n; i++) { + let expected = expectedUpdates[i] ?? AccountUpdate.dummy(); + let actual = forestIterator.next().accountUpdate; + assertEqual(actual, expected); + } + } +); + +// similar to the test before, but now we make all updates in the second layer inaccessible +// so the iteration should walk through the first and second layer +test.custom({ timeBudget: 5000 })( + flatAccountUpdates, + Random.publicKey, + (flatUpdates, publicKey) => { + // create token owner and derive token id + let tokenOwner = PublicKey.fromObject({ + x: Field.from(publicKey.x), + isOdd: Bool(!!publicKey.isOdd), + }); + let tokenId = TokenId.derive(tokenOwner); + + // make all second-level updates inaccessible + flatUpdates + .filter((u) => u.body.callDepth === 1) + .forEach((u, i) => { + if (i % 3 === 0) { + u.body.mayUseToken = AccountUpdate.MayUseToken.No; + } else if (i % 3 === 1) { + u.body.mayUseToken = AccountUpdate.MayUseToken.ParentsOwnToken; + } else { + u.body.publicKey = tokenOwner; + u.body.tokenId = TokenId.default; + } + }); + + // prepare forest iterator from flat account updates + let forest = AccountUpdateForest.fromFlatArray(flatUpdates); + let forestIterator = TokenAccountUpdateIterator.create(forest, tokenId); + + // step through forest iterator and compare against expected updates + let expectedUpdates = flatUpdates.filter((u) => u.body.callDepth <= 1); + + let n = flatUpdates.length; + for (let i = 0; i < n; i++) { + let expected = expectedUpdates[i] ?? AccountUpdate.dummy(); + let actual = forestIterator.next().accountUpdate; + assertEqual(actual, expected); + } + } +); + +// HELPERS + +function accountUpdateFromBigint(a: TypesBigint.AccountUpdate): AccountUpdate { + // bigint to json, then to provable + return AccountUpdate.fromJSON(TypesBigint.AccountUpdate.toJSON(a)); +} + +function assertEqual(actual: AccountUpdate, expected: AccountUpdate) { + let actualHash = hashAccountUpdate(actual).toBigInt(); + let expectedHash = hashAccountUpdate(expected).toBigInt(); + + assert.deepStrictEqual(actual.body, expected.body); + assert.deepStrictEqual( + actual.authorization.proof, + expected.authorization.proof + ); + assert.deepStrictEqual( + actual.authorization.signature, + expected.authorization.signature + ); + assert.deepStrictEqual(actualHash, expectedHash); +} diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts new file mode 100644 index 0000000000..1a9845d51a --- /dev/null +++ b/src/lib/mina/token/token-contract.ts @@ -0,0 +1,189 @@ +import { Bool } from '../../provable/wrapped.js'; +import { UInt64, Int64 } from '../../provable/int.js'; +import { Provable } from '../../provable/provable.js'; +import { PublicKey } from '../../provable/crypto/signature.js'; +import { + AccountUpdate, + AccountUpdateForest, + AccountUpdateTree, + Permissions, + TokenId, +} from '../account-update.js'; +import { DeployArgs, SmartContract } from '../zkapp.js'; +import { TokenAccountUpdateIterator } from './forest-iterator.js'; +import { tokenMethods } from './token-methods.js'; + +export { TokenContract }; + +// it's fine to have this restriction, because the protocol also has a limit of ~20 +// TODO find out precise protocol limit +const MAX_ACCOUNT_UPDATES = 20; + +/** + * Base token contract which + * - implements the `Approvable` API, with the `approveBase()` method left to be defined by subclasses + * - implements the `Transferable` API as a wrapper around the `Approvable` API + */ +abstract class TokenContract extends SmartContract { + // change default permissions - important that token contracts use an access permission + + /** + * Deploys a {@link TokenContract}. + * + * In addition to base smart contract deployment, this adds two steps: + * - set the `access` permission to `proofOrSignature()`, to prevent against unauthorized token operations + * - not doing this would imply that anyone can bypass token contract authorization and simply mint themselves tokens + * - require the zkapp account to be new, using the `isNew` precondition. + * this guarantees that the access permission is set from the very start of the existence of this account. + * creating the zkapp account before deployment would otherwise be a security vulnerability that is too easy to introduce. + * + * Note that because of the `isNew` precondition, the zkapp account must not be created prior to calling `deploy()`. + * + * If the contract needs to be re-deployed, you can switch off this behaviour by overriding the `isNew` precondition: + * ```ts + * async deploy() { + * await super.deploy(); + * // DON'T DO THIS ON THE INITIAL DEPLOYMENT! + * this.account.isNew.requireNothing(); + * } + * ``` + */ + async deploy(args?: DeployArgs) { + await super.deploy(args); + + // set access permission, to prevent unauthorized token operations + this.account.permissions.set({ + ...Permissions.default(), + access: Permissions.proofOrSignature(), + }); + + // assert that this account is new, to ensure unauthorized token operations + // are not possible before this contract is deployed + // see https://github.com/o1-labs/o1js/issues/1439 for details + this.account.isNew.requireEquals(Bool(true)); + } + + /** + * Returns the `tokenId` of the token managed by this contract. + */ + deriveTokenId() { + return TokenId.derive(this.address, this.tokenId); + } + + /** + * Helper methods to use from within a token contract. + */ + get internal() { + return tokenMethods(this.self); + } + + // APPROVABLE API has to be specified by subclasses, + // but the hard part is `forEachUpdate()` + + abstract approveBase(forest: AccountUpdateForest): Promise; + + /** + * Iterate through the account updates in `updates` and apply `callback` to each. + * + * This method is provable and is suitable as a base for implementing `approveUpdates()`. + */ + forEachUpdate( + updates: AccountUpdateForest, + callback: (update: AccountUpdate, usesToken: Bool) => void + ) { + let iterator = TokenAccountUpdateIterator.create( + updates, + this.deriveTokenId() + ); + + // iterate through the forest and apply user-defined logc + for (let i = 0; i < MAX_ACCOUNT_UPDATES; i++) { + let { accountUpdate, usesThisToken } = iterator.next(); + callback(accountUpdate, usesThisToken); + } + + // prove that we checked all updates + iterator.assertFinished( + `Number of account updates to approve exceed ` + + `the supported limit of ${MAX_ACCOUNT_UPDATES}.\n` + ); + + // skip hashing our child account updates in the method wrapper + // since we just did that in the loop above + this.approve(updates); + } + + /** + * Use `forEachUpdate()` to prove that the total balance change of child account updates is zero. + * + * This is provided out of the box as it is both a good example, and probably the most common implementation, of `approveBase()`. + */ + checkZeroBalanceChange(updates: AccountUpdateForest) { + let totalBalanceChange = Int64.zero; + + this.forEachUpdate(updates, (accountUpdate, usesToken) => { + totalBalanceChange = totalBalanceChange.add( + Provable.if(usesToken, accountUpdate.balanceChange, Int64.zero) + ); + }); + + // prove that the total balance change is zero + totalBalanceChange.assertEquals(0); + } + + /** + * Approve a single account update (with arbitrarily many children). + */ + async approveAccountUpdate(accountUpdate: AccountUpdate | AccountUpdateTree) { + let forest = toForest([accountUpdate]); + await this.approveBase(forest); + } + + /** + * Approve a list of account updates (with arbitrarily many children). + */ + async approveAccountUpdates( + accountUpdates: (AccountUpdate | AccountUpdateTree)[] + ) { + let forest = toForest(accountUpdates); + await this.approveBase(forest); + } + + // TRANSFERABLE API - simple wrapper around Approvable API + + /** + * Transfer `amount` of tokens from `from` to `to`. + */ + async transfer( + from: PublicKey | AccountUpdate, + to: PublicKey | AccountUpdate, + amount: UInt64 | number | bigint + ) { + // coerce the inputs to AccountUpdate and pass to `approveBase()` + let tokenId = this.deriveTokenId(); + if (from instanceof PublicKey) { + from = AccountUpdate.defaultAccountUpdate(from, tokenId); + from.requireSignature(); + from.label = `${this.constructor.name}.transfer() (from)`; + } + if (to instanceof PublicKey) { + to = AccountUpdate.defaultAccountUpdate(to, tokenId); + to.label = `${this.constructor.name}.transfer() (to)`; + } + + from.balanceChange = Int64.from(amount).neg(); + to.balanceChange = Int64.from(amount); + + let forest = toForest([from, to]); + await this.approveBase(forest); + } +} + +function toForest( + updates: (AccountUpdate | AccountUpdateTree)[] +): AccountUpdateForest { + let trees = updates.map((a) => + a instanceof AccountUpdate ? a.extractTree() : a + ); + return AccountUpdateForest.from(trees); +} diff --git a/src/lib/mina/token/token-contract.unit-test.ts b/src/lib/mina/token/token-contract.unit-test.ts new file mode 100644 index 0000000000..def7ecd3c7 --- /dev/null +++ b/src/lib/mina/token/token-contract.unit-test.ts @@ -0,0 +1,106 @@ +import assert from 'node:assert'; +import { + method, + Mina, + UInt64, + AccountUpdate, + AccountUpdateForest, + TokenContract, + Int64, + PrivateKey, +} from '../../../index.js'; + +class ExampleTokenContract extends TokenContract { + // APPROVABLE API + + @method + async approveBase(updates: AccountUpdateForest) { + this.checkZeroBalanceChange(updates); + } + + // constant supply + SUPPLY = UInt64.from(10n ** 18n); + + @method + async init() { + super.init(); + + // mint the entire supply to the token account with the same address as this contract + this.internal.mint({ address: this.address, amount: this.SUPPLY }); + } +} + +// TESTS + +let Local = Mina.LocalBlockchain({ proofsEnabled: false }); +Mina.setActiveInstance(Local); + +let [ + { publicKey: sender, privateKey: senderKey }, + { publicKey: otherAddress, privateKey: otherKey }, +] = Local.testAccounts; + +let { publicKey: tokenAddress, privateKey: tokenKey } = + PrivateKey.randomKeypair(); +let token = new ExampleTokenContract(tokenAddress); +let tokenId = token.deriveTokenId(); + +// deploy token contract +let deployTx = await Mina.transaction(sender, async () => { + AccountUpdate.fundNewAccount(sender, 2); + await token.deploy(); +}); +await deployTx.prove(); +await deployTx.sign([tokenKey, senderKey]).send(); + +assert( + Mina.getAccount(tokenAddress).zkapp?.verificationKey !== undefined, + 'token contract deployed' +); + +// can transfer tokens between two accounts +let transferTx = await Mina.transaction(sender, async () => { + AccountUpdate.fundNewAccount(sender); + await token.transfer(tokenAddress, otherAddress, UInt64.one); +}); +await transferTx.prove(); +await transferTx.sign([tokenKey, senderKey]).send(); + +Mina.getBalance(otherAddress, tokenId).assertEquals(UInt64.one); + +// fails to approve a deep account update tree with correct token permissions, but a non-zero balance sum +let update1 = AccountUpdate.create(otherAddress); +update1.body.mayUseToken = AccountUpdate.MayUseToken.ParentsOwnToken; + +let update2 = AccountUpdate.create(otherAddress); +update2.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent; +update2.body.callDepth = 1; + +let update3 = AccountUpdate.create(otherAddress, tokenId); +update3.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent; +update3.balanceChange = Int64.one; +update3.body.callDepth = 2; + +let forest = AccountUpdateForest.fromFlatArray([update1, update2, update3]); + +await assert.rejects( + () => Mina.transaction(sender, () => token.approveBase(forest)), + /Field\.assertEquals\(\): 1 != 0/ +); + +// succeeds to approve deep account update tree with zero balance sum +let update4 = AccountUpdate.createSigned(otherAddress, tokenId); +update4.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent; +update4.balanceChange = Int64.minusOne; +update4.body.callDepth = 2; + +forest = AccountUpdateForest.fromFlatArray([ + update1, + update2, + update3, + update4, +]); + +let approveTx = await Mina.transaction(sender, () => token.approveBase(forest)); +await approveTx.prove(); +await approveTx.sign([senderKey, otherKey]).send(); diff --git a/src/lib/mina/token/token-methods.ts b/src/lib/mina/token/token-methods.ts new file mode 100644 index 0000000000..df7357197a --- /dev/null +++ b/src/lib/mina/token/token-methods.ts @@ -0,0 +1,97 @@ +import { AccountUpdate, Authorization, TokenId } from '../account-update.js'; +import { isSmartContract } from '../smart-contract-base.js'; +import { PublicKey } from '../../provable/crypto/signature.js'; +import type { SmartContract } from '../zkapp.js'; +import { UInt64 } from '../../provable/int.js'; +import { Bool, Field } from '../../provable/wrapped.js'; + +export { tokenMethods }; + +function tokenMethods(self: AccountUpdate) { + return { + /** + * Mints token balance to `address`. Returns the mint account update. + */ + mint({ + address, + amount, + }: { + address: PublicKey | AccountUpdate | SmartContract; + amount: number | bigint | UInt64; + }) { + let id = TokenId.derive(self.publicKey, self.tokenId); + let receiver = getApprovedUpdate(self, id, address, 'token.mint()'); + receiver.balance.addInPlace(amount); + return receiver; + }, + + /** + * Burn token balance on `address`. Returns the burn account update. + */ + burn({ + address, + amount, + }: { + address: PublicKey | AccountUpdate | SmartContract; + amount: number | bigint | UInt64; + }) { + let id = TokenId.derive(self.publicKey, self.tokenId); + let sender = getApprovedUpdate(self, id, address, 'token.burn()'); + + // Sub the amount to burn from the sender's account + sender.balance.subInPlace(amount); + + // Require signature from the sender account being deducted + sender.body.useFullCommitment = Bool(true); + Authorization.setLazySignature(sender); + return sender; + }, + + /** + * Move token balance from `from` to `to`. Returns the `to` account update. + */ + send({ + from, + to, + amount, + }: { + from: PublicKey | AccountUpdate | SmartContract; + to: PublicKey | AccountUpdate | SmartContract; + amount: number | bigint | UInt64; + }) { + let id = TokenId.derive(self.publicKey, self.tokenId); + let sender = getApprovedUpdate(self, id, from, 'token.send() (sender)'); + sender.balance.subInPlace(amount); + sender.body.useFullCommitment = Bool(true); + Authorization.setLazySignature(sender); + + let receiver = getApprovedUpdate(self, id, to, 'token.send() (receiver)'); + receiver.balance.addInPlace(amount); + + return receiver; + }, + }; +} + +// helper + +function getApprovedUpdate( + self: AccountUpdate, + tokenId: Field, + child: PublicKey | AccountUpdate | SmartContract, + label: string +) { + if (isSmartContract(child)) { + child = child.self; + } + if (child instanceof AccountUpdate) { + child.tokenId.assertEquals(tokenId); + self.approve(child); + } + if (child instanceof PublicKey) { + child = AccountUpdate.defaultAccountUpdate(child, tokenId); + self.approve(child); + } + if (!child.label) child.label = `${self.label ?? 'Unlabeled'}.${label}`; + return child; +} diff --git a/src/lib/mina/transaction-context.ts b/src/lib/mina/transaction-context.ts new file mode 100644 index 0000000000..d10a4d7113 --- /dev/null +++ b/src/lib/mina/transaction-context.ts @@ -0,0 +1,16 @@ +import type { AccountUpdateLayout } from './account-update.js'; +import type { PublicKey } from '../provable/crypto/signature.js'; +import { Context } from '../util/global-context.js'; + +export { currentTransaction, CurrentTransaction, FetchMode }; + +type FetchMode = 'fetch' | 'cached' | 'test'; +type CurrentTransaction = { + sender?: PublicKey; + layout: AccountUpdateLayout; + fetchMode: FetchMode; + isFinalRunOutsideCircuit: boolean; + numberOfRuns: 0 | 1 | undefined; +}; + +let currentTransaction = Context.create(); diff --git a/src/lib/mina/transaction-logic/apply.ts b/src/lib/mina/transaction-logic/apply.ts new file mode 100644 index 0000000000..9448444974 --- /dev/null +++ b/src/lib/mina/transaction-logic/apply.ts @@ -0,0 +1,30 @@ +/** + * Apply transactions to a ledger of accounts. + */ +import { type AccountUpdate } from '../account-update.js'; +import { Account } from '../account.js'; + +export { applyAccountUpdate }; + +/** + * Apply a single account update to update an account. + * + * TODO: + * - This must receive and return some context global to the transaction, to check validity + * - Should operate on the value / bigint type, not the provable type + */ +function applyAccountUpdate(account: Account, update: AccountUpdate): Account { + account.publicKey.assertEquals(update.publicKey); + account.tokenId.assertEquals(update.tokenId, 'token id mismatch'); + + // clone account (TODO: do this efficiently) + let json = Account.toJSON(account); + account = Account.fromJSON(json); + + // update permissions + if (update.update.permissions.isSome.toBoolean()) { + account.permissions = update.update.permissions.value; + } + + return account; +} diff --git a/src/lib/mina/transaction-logic/ledger.ts b/src/lib/mina/transaction-logic/ledger.ts new file mode 100644 index 0000000000..0ae1ce4dc3 --- /dev/null +++ b/src/lib/mina/transaction-logic/ledger.ts @@ -0,0 +1,64 @@ +/** + * A ledger of accounts - simple model of a local blockchain. + */ +import { PublicKey } from '../../provable/crypto/signature.js'; +import type { AccountUpdate } from '../account-update.js'; +import { Account, newAccount } from '../account.js'; +import { Field } from '../../provable/field.js'; +import { applyAccountUpdate } from './apply.js'; +import { Types } from '../../../bindings/mina-transaction/types.js'; + +export { SimpleLedger }; + +class SimpleLedger { + accounts: Map; + + constructor() { + this.accounts = new Map(); + } + + static create(): SimpleLedger { + return new SimpleLedger(); + } + + exists({ + publicKey, + tokenId = Types.TokenId.empty(), + }: InputAccountId): boolean { + return this.accounts.has(accountId({ publicKey, tokenId })); + } + + store(account: Account): void { + this.accounts.set(accountId(account), account); + } + + load({ + publicKey, + tokenId = Types.TokenId.empty(), + }: InputAccountId): Account | undefined { + let id = accountId({ publicKey, tokenId }); + let account = this.accounts.get(id); + return account; + } + + apply(update: AccountUpdate): void { + let id = accountId(update.body); + let account = this.accounts.get(id); + account ??= newAccount(update.body); + + let updated = applyAccountUpdate(account, update); + this.accounts.set(id, updated); + } +} + +type AccountId = { publicKey: PublicKey; tokenId: Field }; +type InputAccountId = { publicKey: PublicKey; tokenId?: Field }; + +function accountId(account: AccountId): bigint { + let id = account.publicKey.x.toBigInt(); + id <<= 1n; + id |= BigInt(account.publicKey.isOdd.toBoolean()); + id <<= BigInt(Field.sizeInBits); + id |= account.tokenId.toBigInt(); + return id; +} diff --git a/src/lib/mina/transaction-validation.ts b/src/lib/mina/transaction-validation.ts new file mode 100644 index 0000000000..ddc6a23893 --- /dev/null +++ b/src/lib/mina/transaction-validation.ts @@ -0,0 +1,350 @@ +/** + * This module holds the global Mina instance and its interface. + */ +import { + ZkappCommand, + TokenId, + Events, + ZkappPublicInput, + AccountUpdate, + dummySignature, +} from './account-update.js'; +import { Field } from '../provable/wrapped.js'; +import { UInt64, UInt32 } from '../provable/int.js'; +import { PublicKey } from '../provable/crypto/signature.js'; +import { JsonProof, verify } from '../proof-system/zkprogram.js'; +import { verifyAccountUpdateSignature } from '../../mina-signer/src/sign-zkapp-command.js'; +import { TransactionCost, TransactionLimits } from './constants.js'; +import { cloneCircuitValue } from '../provable/types/struct.js'; +import { assert } from '../provable/gadgets/common.js'; +import { Types, TypesBigint } from '../../bindings/mina-transaction/types.js'; +import type { NetworkId } from '../../mina-signer/src/types.js'; +import type { Account } from './account.js'; +import type { NetworkValue } from './precondition.js'; + +export { + reportGetAccountError, + defaultNetworkState, + verifyTransactionLimits, + verifyAccountUpdate, + filterGroups, +}; + +function reportGetAccountError(publicKey: string, tokenId: string) { + if (tokenId === TokenId.toBase58(TokenId.default)) { + return `getAccount: Could not find account for public key ${publicKey}`; + } else { + return `getAccount: Could not find account for public key ${publicKey} with the tokenId ${tokenId}`; + } +} + +function defaultNetworkState(): NetworkValue { + let epochData: NetworkValue['stakingEpochData'] = { + ledger: { hash: Field(0), totalCurrency: UInt64.zero }, + seed: Field(0), + startCheckpoint: Field(0), + lockCheckpoint: Field(0), + epochLength: UInt32.zero, + }; + return { + snarkedLedgerHash: Field(0), + blockchainLength: UInt32.zero, + minWindowDensity: UInt32.zero, + totalCurrency: UInt64.zero, + globalSlotSinceGenesis: UInt32.zero, + stakingEpochData: epochData, + nextEpochData: cloneCircuitValue(epochData), + }; +} + +function verifyTransactionLimits({ accountUpdates }: ZkappCommand) { + let eventElements = { events: 0, actions: 0 }; + + let authKinds = accountUpdates.map((update) => { + eventElements.events += countEventElements(update.body.events); + eventElements.actions += countEventElements(update.body.actions); + let { isSigned, isProved, verificationKeyHash } = + update.body.authorizationKind; + return { + isSigned: isSigned.toBoolean(), + isProved: isProved.toBoolean(), + verificationKeyHash: verificationKeyHash.toString(), + }; + }); + // insert entry for the fee payer + authKinds.unshift({ + isSigned: true, + isProved: false, + verificationKeyHash: '', + }); + let authTypes = filterGroups(authKinds); + + /* + np := proof + n2 := signedPair + n1 := signedSingle + + formula used to calculate how expensive a zkapp transaction is + + 10.26*np + 10.08*n2 + 9.14*n1 < 69.45 + */ + let totalTimeRequired = + TransactionCost.PROOF_COST * authTypes.proof + + TransactionCost.SIGNED_PAIR_COST * authTypes.signedPair + + TransactionCost.SIGNED_SINGLE_COST * authTypes.signedSingle; + + let isWithinCostLimit = totalTimeRequired < TransactionCost.COST_LIMIT; + + let isWithinEventsLimit = + eventElements.events <= TransactionLimits.MAX_EVENT_ELEMENTS; + let isWithinActionsLimit = + eventElements.actions <= TransactionLimits.MAX_ACTION_ELEMENTS; + + let error = ''; + + if (!isWithinCostLimit) { + // TODO: we should add a link to the docs explaining the reasoning behind it once we have such an explainer + error += `Error: The transaction is too expensive, try reducing the number of AccountUpdates that are attached to the transaction. +Each transaction needs to be processed by the snark workers on the network. +Certain layouts of AccountUpdates require more proving time than others, and therefore are too expensive. + +${JSON.stringify(authTypes)} +\n\n`; + } + + if (!isWithinEventsLimit) { + error += `Error: The account updates in your transaction are trying to emit too much event data. The maximum allowed number of field elements in events is ${TransactionLimits.MAX_EVENT_ELEMENTS}, but you tried to emit ${eventElements.events}.\n\n`; + } + + if (!isWithinActionsLimit) { + error += `Error: The account updates in your transaction are trying to emit too much action data. The maximum allowed number of field elements in actions is ${TransactionLimits.MAX_ACTION_ELEMENTS}, but you tried to emit ${eventElements.actions}.\n\n`; + } + + if (error) throw Error('Error during transaction sending:\n\n' + error); +} + +function countEventElements({ data }: Events) { + return data.reduce((acc, ev) => acc + ev.length, 0); +} + +function filterGroups(xs: AuthorizationKind[]) { + let pairs = filterPairs(xs); + xs = pairs.xs; + + let singleCount = 0; + let proofCount = 0; + + xs.forEach((t) => { + if (t.isProved) proofCount++; + else singleCount++; + }); + + return { + signedPair: pairs.pairs, + signedSingle: singleCount, + proof: proofCount, + }; +} + +async function verifyAccountUpdate( + account: Account, + accountUpdate: AccountUpdate, + publicInput: ZkappPublicInput, + transactionCommitments: { commitment: bigint; fullCommitment: bigint }, + proofsEnabled: boolean, + networkId: NetworkId +): Promise { + // check that that top-level updates have mayUseToken = No + // (equivalent check exists in the Mina node) + if ( + accountUpdate.body.callDepth === 0 && + !AccountUpdate.MayUseToken.isNo(accountUpdate).toBoolean() + ) { + throw Error( + 'Top-level account update can not use or pass on token permissions. Make sure that\n' + + 'accountUpdate.body.mayUseToken = AccountUpdate.MayUseToken.No;' + ); + } + + let perm = account.permissions; + + // check if addMissingSignatures failed to include a signature + // due to a missing private key + if (accountUpdate.authorization === dummySignature()) { + let pk = PublicKey.toBase58(accountUpdate.body.publicKey); + throw Error( + `verifyAccountUpdate: Detected a missing signature for (${pk}), private key was missing.` + ); + } + // we are essentially only checking if the update is empty or an actual update + function includesChange( + val: T | string | null | (string | null)[] + ): boolean { + if (Array.isArray(val)) { + return !val.every((v) => v === null); + } else { + return val !== null; + } + } + + function permissionForUpdate(key: string): Types.AuthRequired { + switch (key) { + case 'appState': + return perm.editState; + case 'delegate': + return perm.setDelegate; + case 'verificationKey': + return perm.setVerificationKey.auth; + case 'permissions': + return perm.setPermissions; + case 'zkappUri': + return perm.setZkappUri; + case 'tokenSymbol': + return perm.setTokenSymbol; + case 'timing': + return perm.setTiming; + case 'votingFor': + return perm.setVotingFor; + case 'actions': + return perm.editActionState; + case 'incrementNonce': + return perm.incrementNonce; + case 'send': + return perm.send; + case 'receive': + return perm.receive; + default: + throw Error(`Invalid permission for field ${key}: does not exist.`); + } + } + + let accountUpdateJson = accountUpdate.toJSON(); + const update = accountUpdateJson.body.update; + + let errorTrace = ''; + + let isValidProof = false; + let isValidSignature = false; + + // we don't check if proofs aren't enabled + if (!proofsEnabled) isValidProof = true; + + if (accountUpdate.authorization.proof && proofsEnabled) { + try { + let publicInputFields = ZkappPublicInput.toFields(publicInput); + + let proof: JsonProof = { + maxProofsVerified: 2, + proof: accountUpdate.authorization.proof!, + publicInput: publicInputFields.map((f) => f.toString()), + publicOutput: [], + }; + + let verificationKey = account.zkapp?.verificationKey?.data; + assert( + verificationKey !== undefined, + 'Account does not have a verification key' + ); + + isValidProof = await verify(proof, verificationKey); + if (!isValidProof) { + throw Error( + `Invalid proof for account update\n${JSON.stringify(update)}` + ); + } + } catch (error) { + errorTrace += '\n\n' + (error as Error).stack; + isValidProof = false; + } + } + + if (accountUpdate.authorization.signature) { + // checking permissions and authorization for each account update individually + try { + isValidSignature = verifyAccountUpdateSignature( + TypesBigint.AccountUpdate.fromJSON(accountUpdateJson), + transactionCommitments, + networkId + ); + } catch (error) { + errorTrace += '\n\n' + (error as Error).stack; + isValidSignature = false; + } + } + + let verified = false; + + function checkPermission(p0: Types.AuthRequired, field: string) { + let p = Types.AuthRequired.toJSON(p0); + if (p === 'None') return; + + if (p === 'Impossible') { + throw Error( + `Transaction verification failed: Cannot update field '${field}' because permission for this field is '${p}'` + ); + } + + if (p === 'Signature' || p === 'Either') { + verified ||= isValidSignature; + } + + if (p === 'Proof' || p === 'Either') { + verified ||= isValidProof; + } + + if (!verified) { + throw Error( + `Transaction verification failed: Cannot update field '${field}' because permission for this field is '${p}', but the required authorization was not provided or is invalid. + ${errorTrace !== '' ? 'Error trace: ' + errorTrace : ''}\n\n` + ); + } + } + + // goes through the update field on a transaction + Object.entries(update).forEach(([key, value]) => { + if (includesChange(value)) { + let p = permissionForUpdate(key); + checkPermission(p, key); + } + }); + + // checks the sequence events (which result in an updated sequence state) + if (accountUpdate.body.actions.data.length > 0) { + let p = permissionForUpdate('actions'); + checkPermission(p, 'actions'); + } + + if (accountUpdate.body.incrementNonce.toBoolean()) { + let p = permissionForUpdate('incrementNonce'); + checkPermission(p, 'incrementNonce'); + } + + // this checks for an edge case where an account update can be approved using proofs but + // a) the proof is invalid (bad verification key) + // and b) there are no state changes initiate so no permissions will be checked + // however, if the verification key changes, the proof should still be invalid + if (errorTrace && !verified) { + throw Error( + `One or more proofs were invalid and no other form of authorization was provided.\n${errorTrace}` + ); + } +} + +type AuthorizationKind = { isProved: boolean; isSigned: boolean }; + +const isPair = (a: AuthorizationKind, b: AuthorizationKind) => + !a.isProved && !b.isProved; + +function filterPairs(xs: AuthorizationKind[]): { + xs: { isProved: boolean; isSigned: boolean }[]; + pairs: number; +} { + if (xs.length <= 1) return { xs, pairs: 0 }; + if (isPair(xs[0], xs[1])) { + let rec = filterPairs(xs.slice(2)); + return { xs: rec.xs, pairs: rec.pairs + 1 }; + } else { + let rec = filterPairs(xs.slice(1)); + return { xs: [xs[0]].concat(rec.xs), pairs: rec.pairs }; + } +} diff --git a/src/lib/mina/transaction.ts b/src/lib/mina/transaction.ts new file mode 100644 index 0000000000..0a5c2bc6c0 --- /dev/null +++ b/src/lib/mina/transaction.ts @@ -0,0 +1,510 @@ +import { + ZkappCommand, + AccountUpdate, + ZkappPublicInput, + AccountUpdateLayout, + FeePayerUnsigned, + addMissingSignatures, + TokenId, + addMissingProofs, +} from './account-update.js'; +import { Field } from '../provable/wrapped.js'; +import { PrivateKey, PublicKey } from '../provable/crypto/signature.js'; +import { UInt32, UInt64 } from '../provable/int.js'; +import { Empty, Proof } from '../proof-system/zkprogram.js'; +import { currentTransaction } from './transaction-context.js'; +import { Provable } from '../provable/provable.js'; +import { assertPreconditionInvariants } from './precondition.js'; +import { Account } from './account.js'; +import { type FeePayerSpec, activeInstance } from './mina-instance.js'; +import * as Fetch from './fetch.js'; +import { type SendZkAppResponse, sendZkappQuery } from './graphql.js'; +import { type FetchMode } from './transaction-context.js'; +import { assertPromise } from '../util/assert.js'; + +export { + type Transaction, + type PendingTransaction, + type IncludedTransaction, + type RejectedTransaction, + type PendingTransactionStatus, + createTransaction, + sendTransaction, + newTransaction, + getAccount, + transaction, + createRejectedTransaction, + createIncludedTransaction, +}; + +/** + * Defines the structure and operations associated with a transaction. + * This type encompasses methods for serializing the transaction, signing it, generating proofs, + * and submitting it to the network. + */ +type Transaction = { + /** + * Transaction structure used to describe a state transition on the Mina blockchain. + */ + transaction: ZkappCommand; + /** + * Serializes the transaction to a JSON string. + * @returns A string representation of the {@link Transaction}. + */ + toJSON(): string; + /** + * Produces a pretty-printed JSON representation of the {@link Transaction}. + * @returns A formatted string representing the transaction in JSON. + */ + toPretty(): any; + /** + * Constructs the GraphQL query string used for submitting the transaction to a Mina daemon. + * @returns The GraphQL query string for the {@link Transaction}. + */ + toGraphqlQuery(): string; + /** + * Signs all {@link AccountUpdate}s included in the {@link Transaction} that require a signature. + * {@link AccountUpdate}s that require a signature can be specified with `{AccountUpdate|SmartContract}.requireSignature()`. + * @param privateKeys The list of keys that should be used to sign the {@link Transaction} + * @returns The {@link Transaction} instance with all required signatures applied. + * @example + * ```ts + * const signedTx = transaction.sign([userPrivateKey]); + * console.log('Transaction signed successfully.'); + * ``` + */ + sign(privateKeys: PrivateKey[]): Transaction; + /** + * Initiates the proof generation process for the {@link Transaction}. This asynchronous operation is + * crucial for zero-knowledge-based transactions, where proofs are required to validate state transitions. + * This can take some time. + * @example + * ```ts + * await transaction.prove(); + * ``` + */ + prove(): Promise<(Proof | undefined)[]>; + /** + * Submits the {@link Transaction} to the network. This method asynchronously sends the transaction + * for processing. If successful, it returns a {@link PendingTransaction} instance, which can be used to monitor the transaction's progress. + * If the transaction submission fails, this method throws an error that should be caught and handled appropriately. + * @returns A promise that resolves to a {@link PendingTransaction} instance representing the submitted transaction if the submission is successful. + * @throws An error if the transaction cannot be sent or processed by the network, containing details about the failure. + * @example + * ```ts + * try { + * const pendingTransaction = await transaction.send(); + * console.log('Transaction sent successfully to the Mina daemon.'); + * } catch (error) { + * console.error('Failed to send transaction to the Mina daemon:', error); + * } + * ``` + */ + send(): Promise; + /** + * Sends the {@link Transaction} to the network. Unlike the standard {@link Transaction.send}, this function does not throw an error if internal errors are detected. Instead, it returns a {@link PendingTransaction} if the transaction is successfully sent for processing or a {@link RejectedTransaction} if it encounters errors during processing or is outright rejected by the Mina daemon. + * @returns {Promise} A promise that resolves to a {@link PendingTransaction} if the transaction is accepted for processing, or a {@link RejectedTransaction} if the transaction fails or is rejected. + * @example + * ```ts + * const result = await transaction.safeSend(); + * if (result.status === 'pending') { + * console.log('Transaction sent successfully to the Mina daemon.'); + * } else if (result.status === 'rejected') { + * console.error('Transaction failed with errors:', result.errors); + * } + * ``` + */ + safeSend(): Promise; +}; + +type PendingTransactionStatus = 'pending' | 'rejected'; +/** + * Represents a transaction that has been submitted to the blockchain but has not yet reached a final state. + * The {@link PendingTransaction} type extends certain functionalities from the base {@link Transaction} type, + * adding methods to monitor the transaction's progress towards being finalized (either included in a block or rejected). + */ +type PendingTransaction = Pick< + Transaction, + 'transaction' | 'toJSON' | 'toPretty' +> & { + /** + * @property {PendingTransactionStatus} status The status of the transaction after being sent to the Mina daemon. + * This property indicates the transaction's initial processing status but does not guarantee its eventual inclusion in a block. + * A status of `pending` suggests the transaction was accepted by the Mina daemon for processing, + * whereas a status of `rejected` indicates that the transaction was not accepted. + * Use the {@link PendingTransaction.wait()} or {@link PendingTransaction.safeWait()} methods to track the transaction's progress towards finalization and to determine whether it's included in a block. + * @example + * ```ts + * if (pendingTransaction.status === 'pending') { + * console.log('Transaction accepted for processing by the Mina daemon.'); + * try { + * await pendingTransaction.wait(); + * console.log('Transaction successfully included in a block.'); + * } catch (error) { + * console.error('Transaction was rejected or failed to be included in a block:', error); + * } + * } else { + * console.error('Transaction was not accepted for processing by the Mina daemon.'); + * } + * ``` + */ + status: PendingTransactionStatus; + + /** + * Waits for the transaction to be included in a block. This method polls the Mina daemon to check the transaction's status, and throws an error if the transaction is rejected. + * @param {Object} [options] Configuration options for polling behavior. + * @param {number} [options.maxAttempts] The maximum number of attempts to check the transaction status. + * @param {number} [options.interval] The interval, in milliseconds, between status checks. + * @returns {Promise} A promise that resolves to the transaction's final state or throws an error. + * @throws {Error} If the transaction is rejected or fails to finalize within the given attempts. + * @example + * ```ts + * try { + * const transaction = await pendingTransaction.wait({ maxAttempts: 10, interval: 2000 }); + * console.log('Transaction included in a block.'); + * } catch (error) { + * console.error('Transaction rejected or failed to finalize:', error); + * } + * ``` + */ + wait(options?: { + maxAttempts?: number; + interval?: number; + }): Promise; + + /** + * Waits for the transaction to be included in a block. This method polls the Mina daemon to check the transaction's status + * @param {Object} [options] Configuration options for polling behavior. + * @param {number} [options.maxAttempts] The maximum number of polling attempts. + * @param {number} [options.interval] The time interval, in milliseconds, between each polling attempt. + * @returns {Promise} A promise that resolves to the transaction's final state. + * @example + * ```ts + * const transaction = await pendingTransaction.wait({ maxAttempts: 5, interval: 1000 }); + * console.log(transaction.status); // 'included' or 'rejected' + * ``` + */ + safeWait(options?: { + maxAttempts?: number; + interval?: number; + }): Promise; + + /** + * Returns the transaction hash as a string identifier. + * @property {string} The hash of the transaction. + * @example + * ```ts + * const txHash = pendingTransaction.hash; + * console.log(`Transaction hash: ${txHash}`); + * ``` + */ + hash: string; + + /** + * Optional. Contains response data from a ZkApp transaction submission. + * + * @property {SendZkAppResponse} [data] The response data from the transaction submission. + */ + data?: SendZkAppResponse; + + /** + * An array of error messages related to the transaction processing. + * + * @property {string[]} errors Descriptive error messages if the transaction encountered issues during processing. + * @example + * ```ts + * if (!pendingTransaction.status === 'rejected') { + * console.error(`Transaction errors: ${pendingTransaction.errors.join(', ')}`); + * } + * ``` + */ + errors: string[]; +}; + +/** + * Represents a transaction that has been successfully included in a block. + */ +type IncludedTransaction = Pick< + PendingTransaction, + 'transaction' | 'toJSON' | 'toPretty' | 'hash' | 'data' +> & { + /** + * @property {string} status The final status of the transaction, indicating successful inclusion in a block. + * @example + * ```ts + * try { + * const includedTx: IncludedTransaction = await pendingTransaction.wait(); + * // If wait() resolves, it means the transaction was successfully included. + * console.log(`Transaction ${includedTx.hash} included in a block.`); + * } catch (error) { + * // If wait() throws, the transaction was not included in a block. + * console.error('Transaction failed to be included in a block:', error); + * } + * ``` + */ + status: 'included'; +}; + +/** + * Represents a transaction that has been rejected and not included in a blockchain block. + */ +type RejectedTransaction = Pick< + PendingTransaction, + 'transaction' | 'toJSON' | 'toPretty' | 'hash' | 'data' +> & { + /** + * @property {string} status The final status of the transaction, specifically indicating that it has been rejected. + * @example + * ```ts + * try { + * const txResult = await pendingTransaction.wait(); + * // This line will not execute if the transaction is rejected, as `.wait()` will throw an error instead. + * console.log(`Transaction ${txResult.hash} was successfully included in a block.`); + * } catch (error) { + * console.error(`Transaction ${error.transaction.hash} was rejected.`); + * error.errors.forEach((error, i) => { + * console.error(`Error ${i + 1}: ${error}`); + * }); + * } + * ``` + */ + status: 'rejected'; + + /** + * @property {string[]} errors An array of error messages detailing the reasons for the transaction's rejection. + */ + errors: string[]; +}; + +async function createTransaction( + feePayer: FeePayerSpec, + f: () => Promise, + numberOfRuns: 0 | 1 | undefined, + { + fetchMode = 'cached' as FetchMode, + isFinalRunOutsideCircuit = true, + proofsEnabled = true, + } = {} +): Promise { + if (currentTransaction.has()) { + throw new Error('Cannot start new transaction within another transaction'); + } + let feePayerSpec: { + sender?: PublicKey; + fee?: number | string | UInt64; + memo?: string; + nonce?: number; + }; + if (feePayer === undefined) { + feePayerSpec = {}; + } else if (feePayer instanceof PublicKey) { + feePayerSpec = { sender: feePayer }; + } else { + feePayerSpec = feePayer; + } + let { sender, fee, memo = '', nonce } = feePayerSpec; + + let transactionId = currentTransaction.enter({ + sender, + layout: new AccountUpdateLayout(), + fetchMode, + isFinalRunOutsideCircuit, + numberOfRuns, + }); + + // run circuit + try { + if (fetchMode === 'test') { + await Provable.runUnchecked(async () => { + await assertPromise(f()); + Provable.asProver(() => { + let tx = currentTransaction.get(); + tx.layout.toConstantInPlace(); + }); + }); + } else { + await assertPromise(f()); + } + } catch (err) { + currentTransaction.leave(transactionId); + throw err; + } + + let accountUpdates = currentTransaction + .get() + .layout.toFlatList({ mutate: true }); + + try { + // check that on-chain values weren't used without setting a precondition + for (let accountUpdate of accountUpdates) { + assertPreconditionInvariants(accountUpdate); + } + } catch (err) { + currentTransaction.leave(transactionId); + throw err; + } + + let feePayerAccountUpdate: FeePayerUnsigned; + if (sender !== undefined) { + // if senderKey is provided, fetch account to get nonce and mark to be signed + let nonce_; + let senderAccount = getAccount(sender, TokenId.default); + + if (nonce === undefined) { + nonce_ = senderAccount.nonce; + } else { + nonce_ = UInt32.from(nonce); + senderAccount.nonce = nonce_; + Fetch.addCachedAccount(senderAccount); + } + feePayerAccountUpdate = AccountUpdate.defaultFeePayer(sender, nonce_); + if (fee !== undefined) { + feePayerAccountUpdate.body.fee = + fee instanceof UInt64 ? fee : UInt64.from(String(fee)); + } + } else { + // otherwise use a dummy fee payer that has to be filled in later + feePayerAccountUpdate = AccountUpdate.dummyFeePayer(); + } + + let transaction: ZkappCommand = { + accountUpdates, + feePayer: feePayerAccountUpdate, + memo, + }; + + currentTransaction.leave(transactionId); + return newTransaction(transaction, proofsEnabled); +} + +function newTransaction(transaction: ZkappCommand, proofsEnabled?: boolean) { + let self: Transaction = { + transaction, + sign(privateKeys: PrivateKey[]) { + self.transaction = addMissingSignatures(self.transaction, privateKeys); + return self; + }, + async prove() { + let { zkappCommand, proofs } = await addMissingProofs(self.transaction, { + proofsEnabled, + }); + self.transaction = zkappCommand; + return proofs; + }, + toJSON() { + let json = ZkappCommand.toJSON(self.transaction); + return JSON.stringify(json); + }, + toPretty() { + return ZkappCommand.toPretty(self.transaction); + }, + toGraphqlQuery() { + return sendZkappQuery(self.toJSON()); + }, + async send() { + const pendingTransaction = await sendTransaction(self); + if (pendingTransaction.errors.length > 0) { + throw Error( + `Transaction failed with errors:\n- ${pendingTransaction.errors.join( + '\n- ' + )}` + ); + } + return pendingTransaction; + }, + async safeSend() { + const pendingTransaction = await sendTransaction(self); + if (pendingTransaction.errors.length > 0) { + return createRejectedTransaction( + pendingTransaction, + pendingTransaction.errors + ); + } + return pendingTransaction; + }, + }; + return self; +} + +/** + * Construct a smart contract transaction. Within the callback passed to this function, + * you can call into the methods of smart contracts. + * + * ``` + * let tx = await Mina.transaction(sender, async () => { + * await myZkapp.update(); + * await someOtherZkapp.someOtherMethod(); + * }); + * ``` + * + * @return A transaction that can subsequently be submitted to the chain. + */ +function transaction( + sender: FeePayerSpec, + f: () => Promise +): Promise; +function transaction(f: () => Promise): Promise; +function transaction( + senderOrF: FeePayerSpec | (() => Promise), + fOrUndefined?: () => Promise +): Promise { + let sender: FeePayerSpec; + let f: () => Promise; + if (fOrUndefined !== undefined) { + sender = senderOrF as FeePayerSpec; + f = fOrUndefined; + } else { + sender = undefined; + f = senderOrF as () => Promise; + } + return activeInstance.transaction(sender, f); +} + +async function sendTransaction(txn: Transaction) { + return await activeInstance.sendTransaction(txn); +} + +/** + * @return The account data associated to the given public key. + */ +function getAccount(publicKey: PublicKey, tokenId?: Field): Account { + return activeInstance.getAccount(publicKey, tokenId); +} + +function createRejectedTransaction( + { + transaction, + data, + toJSON, + toPretty, + hash, + }: Omit, + errors: string[] +): RejectedTransaction { + return { + status: 'rejected', + errors, + transaction, + toJSON, + toPretty, + hash, + data, + }; +} + +function createIncludedTransaction({ + transaction, + data, + toJSON, + toPretty, + hash, +}: Omit): IncludedTransaction { + return { + status: 'included', + transaction, + toJSON, + toPretty, + hash, + data, + }; +} diff --git a/src/lib/zkapp.ts b/src/lib/mina/zkapp.ts similarity index 73% rename from src/lib/zkapp.ts rename to src/lib/mina/zkapp.ts index cd141ae8d0..3a03d83dd5 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/mina/zkapp.ts @@ -1,34 +1,42 @@ -import { Gate, Pickles, ProvablePure } from '../snarky.js'; -import { Field, Bool } from './core.js'; +import 'reflect-metadata'; +import { Gate, Pickles } from '../../snarky.js'; +import { Field, Bool } from '../provable/wrapped.js'; import { AccountUpdate, - AccountUpdatesLayout, Authorization, Body, Events, Permissions, Actions, - SetOrKeep, - smartContractContext, TokenId, ZkappCommand, zkAppProver, ZkappPublicInput, - ZkappStateLength, - SmartContractContext, -} from './account_update.js'; + LazyProof, + AccountUpdateForest, + AccountUpdateLayout, + AccountUpdateTree, +} from './account-update.js'; import { cloneCircuitValue, FlexibleProvablePure, InferProvable, provable, - Struct, - toConstant, -} from './circuit_value.js'; -import { Provable, getBlindingValue, memoizationContext } from './provable.js'; -import * as Encoding from '../bindings/lib/encoding.js'; -import { Poseidon, hashConstant } from './hash.js'; -import { UInt32, UInt64 } from './int.js'; +} from '../provable/types/struct.js'; +import { + Provable, + getBlindingValue, + memoizationContext, +} from '../provable/provable.js'; +import * as Encoding from '../../bindings/lib/encoding.js'; +import { + HashInput, + Poseidon, + hashConstant, + isHashable, + packToFields, +} from '../provable/crypto/poseidon.js'; +import { UInt32, UInt64 } from '../provable/int.js'; import * as Mina from './mina.js'; import { assertPreconditionInvariants, @@ -39,54 +47,66 @@ import { compileProgram, Empty, emptyValue, - GenericArgument, getPreviousProofsForProver, - isAsFields, methodArgumentsToConstant, methodArgumentTypesAndValues, MethodInterface, Proof, sortMethodArguments, -} from './proof_system.js'; -import { PrivateKey, PublicKey } from './signature.js'; +} from '../proof-system/zkprogram.js'; +import { PublicKey } from '../provable/crypto/signature.js'; import { assertStatePrecondition, cleanStatePrecondition } from './state.js'; import { inAnalyze, + inCheckedComputation, inCompile, inProver, - snarkContext, -} from './provable-context.js'; +} from '../provable/core/provable-context.js'; +import { Cache } from '../proof-system/cache.js'; +import { assert } from '../provable/gadgets/common.js'; +import { SmartContractBase } from './smart-contract-base.js'; +import { ZkappStateLength } from './mina-instance.js'; +import { + SmartContractContext, + accountUpdateLayout, + smartContractContext, +} from './smart-contract-context.js'; +import { assertPromise } from '../util/assert.js'; +import { ProvablePure } from '../provable/types/provable-intf.js'; // external API -export { - SmartContract, - method, - DeployArgs, - declareMethods, - Callback, - Account, - VerificationKey, - Reducer, -}; +export { SmartContract, method, DeployArgs, declareMethods, Account, Reducer }; const reservedPropNames = new Set(['_methods', '_']); +type AsyncFunction = (...args: any) => Promise; /** - * A decorator to use in a zkApp to mark a method as callable by anyone. + * A decorator to use in a zkApp to mark a method as provable. * You can use inside your zkApp class as: * * ``` - * \@method myMethod(someArg: Field) { + * \@method async myMethod(someArg: Field) { + * // your code here + * } + * ``` + * + * To return a value from the method, you have to explicitly declare the return type using the {@link method.returns} decorator: + * ``` + * \@method.returns(Field) + * async myMethod(someArg: Field): Promise { * // your code here * } * ``` */ -function method( - target: T & { constructor: any }, - methodName: keyof T & string, - descriptor: PropertyDescriptor +function method( + target: T & { + [k in K]: (...args: any) => Promise; + }, + methodName: K & string & keyof T, + descriptor: PropertyDescriptor, + returnType?: Provable ) { - const ZkappClass = target.constructor; + const ZkappClass = target.constructor as typeof SmartContract; if (reservedPropNames.has(methodName)) { throw Error(`Property name ${methodName} is reserved.`); } @@ -100,11 +120,6 @@ function method( target, methodName ); - let returnType: Provable = Reflect.getMetadata( - 'design:returntype', - target, - methodName - ); class SelfProof extends Proof { static publicInputType = ZkappPublicInput; @@ -125,7 +140,7 @@ function method( SelfProof ); - if (isAsFields(returnType)) { + if (returnType !== undefined) { internalMethodEntry.returnType = returnType; methodEntry.returnType = returnType; } @@ -137,19 +152,47 @@ function method( ZkappClass._maxProofsVerified = Math.max( ZkappClass._maxProofsVerified, methodEntry.proofArgs.length - ); - let func = descriptor.value; + ) as 0 | 1 | 2; + let func = descriptor.value as AsyncFunction; descriptor.value = wrapMethod(func, ZkappClass, internalMethodEntry); } +/** + * A decorator to mark a zkApp method as provable, and declare its return type. + * + * ``` + * \@method.returns(Field) + * async myMethod(someArg: Field): Promise { + * // your code here + * } + * ``` + */ +method.returns = function ( + returnType: Provable +) { + return function decorateMethod( + target: T & { + [k in K]: (...args: any) => Promise; + }, + methodName: K & string & keyof T, + descriptor: PropertyDescriptor + ) { + return method(target as any, methodName, descriptor, returnType); + }; +}; + // do different things when calling a method, depending on the circumstance function wrapMethod( - method: Function, + method: AsyncFunction, ZkappClass: typeof SmartContract, methodIntf: MethodInterface ) { let methodName = methodIntf.methodName; - return function wrappedMethod(this: SmartContract, ...actualArgs: any[]) { + let noPromiseError = `Expected \`${ZkappClass.name}.${methodName}()\` to return a promise.`; + return async function wrappedMethod( + this: SmartContract, + ...actualArgs: any[] + ) { cleanStatePrecondition(this); // special case: any AccountUpdate that is passed as an argument to a method // is unlinked from its current location, to allow the method to link it to itself @@ -161,12 +204,10 @@ function wrapMethod( let insideContract = smartContractContext.get(); if (!insideContract) { - const context: SmartContractContext = { - this: this, - methodCallDepth: 0, - selfUpdate: selfAccountUpdate(this, methodName), - }; - let id = smartContractContext.enter(context); + const { id, context } = SmartContractContext.enter( + this, + selfAccountUpdate(this, methodName) + ); try { if (inCompile() || inProver() || inAnalyze()) { // important to run this with a fresh accountUpdate everytime, otherwise compile messes up our circuits @@ -174,7 +215,8 @@ function wrapMethod( let proverData = inProver() ? zkAppProver.getData() : undefined; let txId = Mina.currentTransaction.enter({ sender: proverData?.transaction.feePayer.body.publicKey, - accountUpdates: [], + // TODO could pass an update with the fee payer's content here? probably not bc it's not accessed + layout: new AccountUpdateLayout(), fetchMode: inProver() ? 'cached' : 'test', isFinalRunOutsideCircuit: false, numberOfRuns: undefined, @@ -189,14 +231,18 @@ function wrapMethod( let blindingValue = Provable.witness(Field, getBlindingValue); // it's also good if we prove that we use the same blinding value across the method // that's why we pass the variable (not the constant) into a new context - let context = memoizationContext() ?? { + let memoCtx = memoizationContext() ?? { memoized: [], currentIndex: 0, }; - let id = memoizationContext.enter({ ...context, blindingValue }); + let id = memoizationContext.enter({ ...memoCtx, blindingValue }); let result: unknown; try { - result = method.apply(this, actualArgs.map(cloneCircuitValue)); + let clonedArgs = actualArgs.map(cloneCircuitValue); + result = await assertPromise( + method.apply(this, clonedArgs), + noPromiseError + ); } finally { memoizationContext.leave(id); } @@ -209,65 +255,11 @@ function wrapMethod( blindingValue ); accountUpdate.body.callData = Poseidon.hash(callDataFields); - Authorization.setProofAuthorizationKind(accountUpdate); - - // TODO: currently commented out, but could come back in some form when we add caller to the public input - // // compute `caller` field from `isDelegateCall` and a context determined by the transaction - // let callerContext = Provable.witness( - // CallForest.callerContextType, - // () => { - // let { accountUpdate } = zkAppProver.getData(); - // return CallForest.computeCallerContext(accountUpdate); - // } - // ); - // CallForest.addCallers([accountUpdate], callerContext); - - // connect the public input to the account update & child account updates we created - if (DEBUG_PUBLIC_INPUT_CHECK) { - Provable.asProver(() => { - // TODO: print a nice diff string instead of the two objects - // something like `expect` or `json-diff`, but web-compatible - function diff(prover: any, input: any) { - delete prover.id; - delete prover.callDepth; - delete input.id; - delete input.callDepth; - if (JSON.stringify(prover) !== JSON.stringify(input)) { - console.log( - 'transaction:', - ZkappCommand.toPretty(transaction) - ); - console.log('index', index); - console.log('inconsistent account updates:'); - console.log('update created by the prover:'); - console.log(prover); - console.log('update created in transaction block:'); - console.log(input); - } - } - function diffRecursive( - prover: AccountUpdate, - input: AccountUpdate - ) { - diff(prover.toPretty(), input.toPretty()); - let nChildren = input.children.accountUpdates.length; - for (let i = 0; i < nChildren; i++) { - let inputChild = input.children.accountUpdates[i]; - let child = prover.children.accountUpdates[i]; - if (!inputChild || !child) return; - diffRecursive(child, inputChild); - } - } - - let { - accountUpdate: inputUpdate, - transaction, - index, - } = zkAppProver.getData(); - diffRecursive(accountUpdate, inputUpdate); - }); - } - checkPublicInput(publicInput, accountUpdate); + ProofAuthorization.setKind(accountUpdate); + + debugPublicInput(accountUpdate); + let calls = context.selfLayout.finalizeChildren(); + checkPublicInput(publicInput, accountUpdate, calls); // check the self accountUpdate right after calling the method // TODO: this needs to be done in a unified way for all account updates that are created @@ -280,7 +272,10 @@ function wrapMethod( } } else if (!Mina.currentTransaction.has()) { // outside a transaction, just call the method, but check precondition invariants - let result = method.apply(this, actualArgs); + let result = await assertPromise( + method.apply(this, actualArgs), + noPromiseError + ); // check the self accountUpdate right after calling the method // TODO: this needs to be done in a unified way for all account updates that are created assertPreconditionInvariants(this.self); @@ -291,10 +286,9 @@ function wrapMethod( // called smart contract at the top level, in a transaction! // => attach ours to the current list of account updates let accountUpdate = context.selfUpdate; - Mina.currentTransaction()?.accountUpdates.push(accountUpdate); // first, clone to protect against the method modifying arguments! - // TODO: double-check that this works on all possible inputs, e.g. CircuitValue, snarkyjs primitives + // TODO: double-check that this works on all possible inputs, e.g. CircuitValue, o1js primitives let clonedArgs = cloneCircuitValue(actualArgs); // we run this in a "memoization context" so that we can remember witnesses for reuse when proving @@ -303,16 +297,19 @@ function wrapMethod( let memoId = memoizationContext.enter(memoContext); let result: any; try { - result = method.apply( - this, - actualArgs.map((a, i) => { - let arg = methodIntf.allArgs[i]; - if (arg.type === 'witness') { - let type = methodIntf.witnessArgs[arg.index]; - return Provable.witness(type, () => a); - } - return a; - }) + result = await assertPromise( + method.apply( + this, + actualArgs.map((a, i) => { + let arg = methodIntf.allArgs[i]; + if (arg.type === 'witness') { + let type = methodIntf.witnessArgs[arg.index]; + return Provable.witness(type, () => a); + } + return a; + }) + ), + noPromiseError ); } finally { memoizationContext.leave(memoId); @@ -331,7 +328,7 @@ function wrapMethod( accountUpdate.body.callData = Poseidon.hash(callDataFields); if (!Authorization.hasAny(accountUpdate)) { - Authorization.setLazyProof( + ProofAuthorization.setLazyProof( accountUpdate, { methodName: methodIntf.methodName, @@ -345,9 +342,24 @@ function wrapMethod( memoized, blindingValue, }, - Mina.currentTransaction()!.accountUpdates + Mina.currentTransaction.get().layout ); } + + // transfer layout from the smart contract context to the transaction + if (inCheckedComputation()) { + Provable.asProver(() => { + accountUpdate = Provable.toConstant(AccountUpdate, accountUpdate); + context.selfLayout.toConstantInPlace(); + }); + } + let txLayout = Mina.currentTransaction.get().layout; + txLayout.pushTopLevel(accountUpdate); + txLayout.setChildren( + accountUpdate, + context.selfLayout.finalizeChildren() + ); + return result; } } finally { @@ -357,40 +369,20 @@ function wrapMethod( // if we're here, this method was called inside _another_ smart contract method let parentAccountUpdate = insideContract.this.self; - let methodCallDepth = insideContract.methodCallDepth; - let innerContext: SmartContractContext = { - this: this, - methodCallDepth: methodCallDepth + 1, - selfUpdate: selfAccountUpdate(this, methodName), - }; - let id = smartContractContext.enter(innerContext); + + let { id, context: innerContext } = SmartContractContext.enter( + this, + selfAccountUpdate(this, methodName) + ); try { - // if the call result is not undefined but there's no known returnType, the returnType was probably not annotated properly, - // so we have to explain to the user how to do that - let { returnType } = methodIntf; - let noReturnTypeError = - `To return a result from ${methodIntf.methodName}() inside another zkApp, you need to declare the return type.\n` + - `This can be done by annotating the type at the end of the function signature. For example:\n\n` + - `@method ${methodIntf.methodName}(): Field {\n` + - ` // ...\n` + - `}\n\n` + - `Note: Only types built out of \`Field\` are valid return types. This includes snarkyjs primitive types and custom CircuitValues.`; - // if we're lucky, analyzeMethods was already run on the callee smart contract, and we can catch this error early - if ( - ZkappClass._methodMetadata?.[methodIntf.methodName]?.hasReturn && - returnType === undefined - ) { - throw Error(noReturnTypeError); - } // we just reuse the blinding value of the caller for the callee let blindingValue = getBlindingValue(); - let runCalledContract = () => { + let runCalledContract = async () => { let constantArgs = methodArgumentsToConstant(methodIntf, actualArgs); let constantBlindingValue = blindingValue.toConstant(); let accountUpdate = this.self; accountUpdate.body.callDepth = parentAccountUpdate.body.callDepth + 1; - accountUpdate.parent = parentAccountUpdate; let memoContext = { memoized: [], @@ -400,7 +392,10 @@ function wrapMethod( let memoId = memoizationContext.enter(memoContext); let result: any; try { - result = method.apply(this, constantArgs.map(cloneCircuitValue)); + result = await assertPromise( + method.apply(this, constantArgs.map(cloneCircuitValue)), + noPromiseError + ); } finally { memoizationContext.leave(memoId); } @@ -408,11 +403,12 @@ function wrapMethod( assertStatePrecondition(this); if (result !== undefined) { - if (returnType === undefined) { - throw Error(noReturnTypeError); - } else { - result = toConstant(returnType, result); - } + let { returnType } = methodIntf; + assert( + returnType !== undefined, + "Bug: returnType is undefined but the method result isn't." + ); + result = Provable.toConstant(returnType, result); } // store inputs + result in callData @@ -425,7 +421,7 @@ function wrapMethod( accountUpdate.body.callData = hashConstant(callDataFields); if (!Authorization.hasAny(accountUpdate)) { - Authorization.setLazyProof( + ProofAuthorization.setLazyProof( accountUpdate, { methodName: methodIntf.methodName, @@ -438,23 +434,33 @@ function wrapMethod( memoized, blindingValue: constantBlindingValue, }, - Mina.currentTransaction()!.accountUpdates + Mina.currentTransaction()?.layout ?? new AccountUpdateLayout() ); } - return { accountUpdate, result: result ?? null }; + // extract callee's account update layout + let children = innerContext.selfLayout.finalizeChildren(); + + return { + accountUpdate, + result: { result: result ?? null, children }, + }; }; // we have to run the called contract inside a witness block, to not affect the caller's circuit - // however, if this is a nested call -- the caller is already called by another contract --, - // then we're already in a witness block, and shouldn't open another one - let { accountUpdate, result } = - methodCallDepth === 0 - ? AccountUpdate.witness( - returnType ?? provable(null), - runCalledContract, - { skipCheck: true } - ) - : runCalledContract(); + let { + accountUpdate, + result: { result, children }, + } = await AccountUpdate.witness<{ + result: any; + children: AccountUpdateForest; + }>( + provable({ + result: methodIntf.returnType ?? provable(null), + children: AccountUpdateForest.provable, + }), + runCalledContract, + { skipCheck: true } + ); // we're back in the _caller's_ circuit now, where we assert stuff about the method call @@ -463,16 +469,19 @@ function wrapMethod( // connect accountUpdate to our own. outside Provable.witness so compile knows the right structure when hashing children accountUpdate.body.callDepth = parentAccountUpdate.body.callDepth + 1; - accountUpdate.parent = parentAccountUpdate; - // beware: we don't include the callee's children in the caller circuit - // nothing is asserted about them -- it's the callee's task to check their children - accountUpdate.children.callsType = { type: 'Witness' }; - parentAccountUpdate.children.accountUpdates.push(accountUpdate); + + insideContract.selfLayout.pushTopLevel(accountUpdate); + insideContract.selfLayout.setChildren(accountUpdate, children); // assert that we really called the right zkapp accountUpdate.body.publicKey.assertEquals(this.address); accountUpdate.body.tokenId.assertEquals(this.self.body.tokenId); + // assert that the callee account update has proof authorization. everything else would have much worse security trade-offs, + // because a one-time change of the callee semantics by using a signature could go unnoticed even if we monitor the callee's + // onchain verification key + assert(accountUpdate.body.authorizationKind.isProved, 'callee is proved'); + // assert that the inputs & outputs we have match what the callee put on its callData let callDataFields = computeCallData( methodIntf, @@ -491,17 +500,17 @@ function wrapMethod( function checkPublicInput( { accountUpdate, calls }: ZkappPublicInput, - self: AccountUpdate + self: AccountUpdate, + selfCalls: AccountUpdateForest ) { - let otherInput = self.toPublicInput(); - accountUpdate.assertEquals(otherInput.accountUpdate); - calls.assertEquals(otherInput.calls); + accountUpdate.assertEquals(self.hash()); + calls.assertEquals(selfCalls.hash); } /** * compute fields to be hashed as callData, in a way that the hash & circuit changes whenever * the method signature changes, i.e., the argument / return types represented as lists of field elements and the methodName. - * see https://github.com/o1-labs/snarkyjs/issues/303#issuecomment-1196441140 + * see https://github.com/o1-labs/o1js/issues/303#issuecomment-1196441140 */ function computeCallData( methodIntf: MethodInterface, @@ -511,16 +520,30 @@ function computeCallData( ) { let { returnType, methodName } = methodIntf; let args = methodArgumentTypesAndValues(methodIntf, argumentValues); - let argSizesAndFields: Field[][] = args.map(({ type, value }) => [ - Field(type.sizeInFields()), - ...type.toFields(value), - ]); + + let input: HashInput = { fields: [], packed: [] }; + for (let { type, value } of args) { + if (isHashable(type)) { + input = HashInput.append(input, type.toInput(value)); + } else { + input.fields!.push( + ...[Field(type.sizeInFields()), ...type.toFields(value)] + ); + } + } + const totalArgFields = packToFields(input); let totalArgSize = Field( args.map(({ type }) => type.sizeInFields()).reduce((s, t) => s + t, 0) ); - let totalArgFields = argSizesAndFields.flat(); + let returnSize = Field(returnType?.sizeInFields() ?? 0); - let returnFields = returnType?.toFields(returnValue) ?? []; + input = { fields: [], packed: [] }; + if (isHashable(returnType)) { + input = HashInput.append(input, returnType.toInput(returnValue)); + } else { + input.fields!.push(...(returnType?.toFields(returnValue) ?? [])); + } + let returnFields = packToFields(input); let methodNameFields = Encoding.stringToFields(methodName); return [ // we have to encode the sizes of arguments / return value, so that fields can't accidentally shift @@ -536,57 +559,6 @@ function computeCallData( ]; } -class Callback extends GenericArgument { - instance: SmartContract; - methodIntf: MethodInterface & { returnType: Provable }; - args: any[]; - - result?: Result; - accountUpdate: AccountUpdate; - - static create( - instance: T, - methodName: K, - args: T[K] extends (...args: infer A) => any ? A : never - ) { - let ZkappClass = instance.constructor as typeof SmartContract; - let methodIntf_ = (ZkappClass._methods ?? []).find( - (i) => i.methodName === methodName - ); - if (methodIntf_ === undefined) - throw Error( - `Callback: could not find method ${ZkappClass.name}.${String( - methodName - )}` - ); - let methodIntf = { - ...methodIntf_, - returnType: methodIntf_.returnType ?? provable(null), - }; - - // call the callback, leveraging composability (if this is inside a smart contract method) - // to prove to the outer circuit that we called it - let result = (instance[methodName] as Function)(...args); - let accountUpdate = instance.self; - - let callback = new Callback({ - instance, - methodIntf, - args, - result, - accountUpdate, - isEmpty: false, - }); - - return callback; - } - - private constructor(self: Callback) { - super(); - Object.assign(this, self); - } -} - /** * The main zkapp class. To write a zkapp, extend this class as such: * @@ -597,7 +569,7 @@ class Callback extends GenericArgument { * ``` * */ -class SmartContract { +class SmartContract extends SmartContractBase { address: PublicKey; tokenId: Field; @@ -614,7 +586,6 @@ class SmartContract { actions: number; rows: number; digest: string; - hasReturn: boolean; gates: Gate[]; } >; // keyed by method name @@ -635,6 +606,7 @@ class SmartContract { } constructor(address: PublicKey, tokenId?: Field) { + super(); this.address = address; this.tokenId = tokenId ?? TokenId.default; Object.defineProperty(this, 'reducer', { @@ -661,36 +633,35 @@ class SmartContract { * it so that proofs end up in the original finite field). These are fairly expensive operations, so **expect compiling to take at least 20 seconds**, * up to several minutes if your circuit is large or your hardware is not optimal for these operations. */ - static async compile() { + static async compile({ + cache = Cache.FileSystemDefault, + forceRecompile = false, + } = {}) { let methodIntfs = this._methods ?? []; let methods = methodIntfs.map(({ methodName }) => { - return ( + return async ( publicInput: unknown, publicKey: PublicKey, tokenId: Field, ...args: unknown[] ) => { let instance = new this(publicKey, tokenId); - (instance as any)[methodName](publicInput, ...args); + await (instance as any)[methodName](publicInput, ...args); }; }); // run methods once to get information that we need already at compile time - this.analyzeMethods(); - let { - verificationKey: verificationKey_, - provers, - verify, - } = await compileProgram( - ZkappPublicInput, - Empty, + let methodsMeta = await this.analyzeMethods(); + let gates = methodIntfs.map((intf) => methodsMeta[intf.methodName].gates); + let { verificationKey, provers, verify } = await compileProgram({ + publicInputType: ZkappPublicInput, + publicOutputType: Empty, methodIntfs, methods, - this - ); - let verificationKey = { - data: verificationKey_.data, - hash: Field(verificationKey_.hash), - } satisfies VerificationKey; + gates, + proofSystemTag: this, + cache, + forceRecompile, + }); this._provers = provers; this._verificationKey = verificationKey; // TODO: instead of returning provers, return an artifact from which provers can be recovered @@ -703,9 +674,9 @@ class SmartContract { * a cached verification key can be used. * @returns the digest, as a hex string */ - static digest() { + static async digest() { // TODO: this should use the method digests in a deterministic order! - let methodData = this.analyzeMethods(); + let methodData = await this.analyzeMethods(); let hash = hashConstant( Object.values(methodData).map((d) => Field(BigInt('0x' + d.digest))) ); @@ -716,21 +687,19 @@ class SmartContract { * Deploys a {@link SmartContract}. * * ```ts - * let tx = await Mina.transaction(sender, () => { + * let tx = await Mina.transaction(sender, async () => { * AccountUpdate.fundNewAccount(sender); - * zkapp.deploy(); + * await zkapp.deploy(); * }); * tx.sign([senderKey, zkAppKey]); * ``` */ - deploy({ + async deploy({ verificationKey, - zkappKey, }: { verificationKey?: { data: string; hash: Field | string }; - zkappKey?: PrivateKey; } = {}) { - let accountUpdate = this.newSelf(); + let accountUpdate = this.newSelf('deploy'); verificationKey ??= (this.constructor as typeof SmartContract) ._verificationKey; if (verificationKey === undefined) { @@ -748,7 +717,7 @@ class SmartContract { let hash = Field.from(hash_); accountUpdate.account.verificationKey.set({ hash, data }); accountUpdate.account.permissions.set(Permissions.default()); - accountUpdate.sign(zkappKey); + accountUpdate.requireSignature(); AccountUpdate.attachToTransaction(accountUpdate); // init if this account is not yet deployed or has no verification key on it @@ -756,7 +725,7 @@ class SmartContract { !Mina.hasAccount(this.address) || Mina.getAccount(this.address).zkapp?.verificationKey === undefined; if (!shouldInit) return; - else this.init(); + else await this.init(); let initUpdate = this.self; // switch back to the deploy account update so the user can make modifications to it this.#executionState = { @@ -796,7 +765,7 @@ super.init(); */ init() { // let accountUpdate = this.newSelf(); // this would emulate the behaviour of init() being a @method - this.account.provedState.assertEquals(Bool(false)); + this.account.provedState.requireEquals(Bool(false)); let accountUpdate = this.self; for (let i = 0; i < ZkappStateLength; i++) { AccountUpdate.setValue(accountUpdate.body.update.appState[i], Field(0)); @@ -818,12 +787,7 @@ super.init(); requireSignature() { this.self.requireSignature(); } - /** - * @deprecated `this.sign()` is deprecated in favor of `this.requireSignature()` - */ - sign(zkappKey?: PrivateKey) { - this.self.sign(zkappKey); - } + /** * Use this command if the account update created by this SmartContract should have no authorization on it, * instead of being authorized with a proof. @@ -870,45 +834,54 @@ super.init(); this.#executionState = { transactionId, accountUpdate }; return accountUpdate; } - // same as this.self, but explicitly creates a _new_ account update + /** * Same as `SmartContract.self` but explicitly creates a new {@link AccountUpdate}. */ - newSelf(): AccountUpdate { + newSelf(methodName?: string): AccountUpdate { let inTransaction = Mina.currentTransaction.has(); let transactionId = inTransaction ? Mina.currentTransaction.id() : NaN; - let accountUpdate = selfAccountUpdate(this); + let accountUpdate = selfAccountUpdate(this, methodName); this.#executionState = { transactionId, accountUpdate }; return accountUpdate; } #_senderState: { sender: PublicKey; transactionId: number }; - /** - * The public key of the current transaction's sender account. - * - * Throws an error if not inside a transaction, or the sender wasn't passed in. - * - * **Warning**: The fact that this public key equals the current sender is not part of the proof. - * A malicious prover could use any other public key without affecting the validity of the proof. - */ - get sender(): PublicKey { - // TODO this logic now has some overlap with this.self, we should combine them somehow - // (but with care since the logic in this.self is a bit more complicated) - if (!Mina.currentTransaction.has()) { - throw Error( - `this.sender is not available outside a transaction. Make sure you only use it within \`Mina.transaction\` blocks or smart contract methods.` - ); - } - let transactionId = Mina.currentTransaction.id(); - if (this.#_senderState?.transactionId === transactionId) { - return this.#_senderState.sender; - } else { - let sender = Provable.witness(PublicKey, () => Mina.sender()); - this.#_senderState = { transactionId, sender }; + sender = { + self: this as SmartContract, + /** + * The public key of the current transaction's sender account. + * + * Throws an error if not inside a transaction, or the sender wasn't passed in. + * + * **Warning**: The fact that this public key equals the current sender is not part of the proof. + * A malicious prover could use any other public key without affecting the validity of the proof. + */ + getUnconstrained(): PublicKey { + // TODO this logic now has some overlap with this.self, we should combine them somehow + // (but with care since the logic in this.self is a bit more complicated) + if (!Mina.currentTransaction.has()) { + throw Error( + `this.sender is not available outside a transaction. Make sure you only use it within \`Mina.transaction\` blocks or smart contract methods.` + ); + } + let transactionId = Mina.currentTransaction.id(); + if (this.self.#_senderState?.transactionId === transactionId) { + return this.self.#_senderState.sender; + } else { + let sender = Provable.witness(PublicKey, () => Mina.sender()); + this.self.#_senderState = { transactionId, sender }; + return sender; + } + }, + + getAndRequireSignature(): PublicKey { + let sender = this.getUnconstrained(); + AccountUpdate.createSigned(sender); return sender; - } - } + }, + }; /** * Current account of the {@link SmartContract}. @@ -930,45 +903,30 @@ super.init(); get currentSlot() { return this.self.currentSlot; } - /** - * Token of the {@link SmartContract}. - */ - get token() { - return this.self.token(); - } /** - * Approve an account update or callback. This will include the account update in the zkApp's public input, - * which means it allows you to read and use its content in a proof, make assertions about it, and modify it. - * - * If this is called with a callback as the first parameter, it will first extract the account update produced by that callback. - * The extracted account update is returned. + * Approve an account update or tree / forest of updates. Doing this means you include the account update in the zkApp's public input, + * which allows you to read and use its content in a proof, make assertions about it, and modify it. * * ```ts - * \@method myApprovingMethod(callback: Callback) { - * let approvedUpdate = this.approve(callback); + * `@method` myApprovingMethod(update: AccountUpdate) { + * this.approve(update); + * + * // read balance on the account (for example) + * let balance = update.account.balance.getAndRequireEquals(); * } * ``` * * Under the hood, "approving" just means that the account update is made a child of the zkApp in the - * tree of account updates that forms the transaction. - * The second parameter `layout` allows you to also make assertions about the approved update's _own_ children, - * by specifying a certain expected layout of children. See {@link AccountUpdate.Layout}. + * tree of account updates that forms the transaction. Similarly, if you pass in an {@link AccountUpdateTree}, + * the entire tree will become a subtree of the zkApp's account update. * - * @param updateOrCallback - * @param layout - * @returns The account update that was approved (needed when passing in a Callback) + * Passing in a forest is a bit different, because it means you set the entire children of the zkApp's account update + * at once. `approve()` will fail if the zkApp's account update already has children, to prevent you from accidentally + * excluding important information from the public input. */ - approve( - updateOrCallback: AccountUpdate | Callback, - layout?: AccountUpdatesLayout - ) { - let accountUpdate = - updateOrCallback instanceof AccountUpdate - ? updateOrCallback - : Provable.witness(AccountUpdate, () => updateOrCallback.accountUpdate); - this.self.approve(accountUpdate, layout); - return accountUpdate; + approve(update: AccountUpdate | AccountUpdateTree | AccountUpdateForest) { + this.self.approve(update); } send(args: { @@ -978,12 +936,6 @@ super.init(); return this.self.send(args); } - /** - * @deprecated use `this.account.tokenSymbol` - */ - get tokenSymbol() { - return this.self.tokenSymbol; - } /** * Balance of this {@link SmartContract}. */ @@ -1161,11 +1113,10 @@ super.init(); * @returns an object, keyed by method name, each entry containing: * - `rows` the size of the constraint system created by this method * - `digest` a digest of the method circuit - * - `hasReturn` a boolean indicating whether the method returns a value * - `actions` the number of actions the method dispatches * - `gates` the constraint system, represented as an array of gates */ - static analyzeMethods() { + static async analyzeMethods({ printSummary = false } = {}) { let ZkappClass = this as typeof SmartContract; let methodMetadata = (ZkappClass._methodMetadata ??= {}); let methodIntfs = ZkappClass._methods ?? []; @@ -1173,26 +1124,18 @@ super.init(); !methodIntfs.every((m) => m.methodName in methodMetadata) && !inAnalyze() ) { - if (snarkContext.get().inRunAndCheck) { - let err = new Error( - 'Can not analyze methods inside Provable.runAndCheck, because this creates a circuit nested in another circuit' - ); - // EXCEPT if the code that calls this knows that it can first run `analyzeMethods` OUTSIDE runAndCheck and try again - (err as any).bootstrap = () => ZkappClass.analyzeMethods(); - throw err; - } let id: number; let insideSmartContract = !!smartContractContext.get(); if (insideSmartContract) id = smartContractContext.enter(null); try { for (let methodIntf of methodIntfs) { let accountUpdate: AccountUpdate; - let { rows, digest, result, gates } = analyzeMethod( + let { rows, digest, gates, summary } = await analyzeMethod( ZkappPublicInput, methodIntf, - (publicInput, publicKey, tokenId, ...args) => { + async (publicInput, publicKey, tokenId, ...args) => { let instance: SmartContract = new ZkappClass(publicKey, tokenId); - let result = (instance as any)[methodIntf.methodName]( + let result = await (instance as any)[methodIntf.methodName]( publicInput, ...args ); @@ -1204,9 +1147,9 @@ super.init(); actions: accountUpdate!.body.actions.data.length, rows, digest, - hasReturn: result !== undefined, gates, }; + if (printSummary) console.log(methodIntf.methodName, summary()); } } finally { if (insideSmartContract) smartContractContext.leave(id!); @@ -1214,20 +1157,6 @@ super.init(); } return methodMetadata; } - - /** - * @deprecated use `this.account..set()` - */ - setValue(maybeValue: SetOrKeep, value: T) { - AccountUpdate.setValue(maybeValue, value); - } - - /** - * @deprecated use `this.account.permissions.set()` - */ - setPermissions(permissions: Permissions) { - this.self.account.permissions.set(permissions); - } } type Reducer = { @@ -1273,6 +1202,7 @@ type ReducerReturn = { initial: { state: State; actionState: Field }, options?: { maxTransactionsWithActions?: number; + maxActionsPerMethod?: number; skipActionStatePrecondition?: boolean; } ): { state: State; actionState: Field }; @@ -1288,6 +1218,7 @@ type ReducerReturn = { fromActionState: Field, options?: { maxTransactionsWithActions?: number; + maxActionsPerMethod?: number; skipActionStatePrecondition?: boolean; } ): Field; @@ -1350,6 +1281,7 @@ class ${contract.constructor.name} extends SmartContract { { state, actionState }: { state: S; actionState: Field }, { maxTransactionsWithActions = 32, + maxActionsPerMethod = 1, skipActionStatePrecondition = false, } = {} ): { state: S; actionState: Field } { @@ -1359,13 +1291,11 @@ class ${contract.constructor.name} extends SmartContract { Use the optional \`maxTransactionsWithActions\` argument to increase this number.` ); } - let methodData = ( - contract.constructor as typeof SmartContract - ).analyzeMethods(); - let possibleActionsPerTransaction = [ - ...new Set(Object.values(methodData).map((o) => o.actions)).add(0), - ].sort((x, y) => x - y); - + // TODO find out max actions per method automatically? + let possibleActionsPerTransaction = Array.from( + { length: maxActionsPerMethod + 1 }, + (_, i) => i + ); let possibleActionTypes = possibleActionsPerTransaction.map((n) => Provable.Array(reducer.actionType, n) ); @@ -1411,7 +1341,7 @@ Use the optional \`maxTransactionsWithActions\` argument to increase this number state = Provable.switch(lengths, stateType, newStates); } if (!skipActionStatePrecondition) { - contract.account.actionState.assertEquals(actionState); + contract.account.actionState.requireEquals(actionState); } return { state, actionState }; }, @@ -1422,15 +1352,15 @@ Use the optional \`maxTransactionsWithActions\` argument to increase this number fromActionState: Field, config ): Field { - const stateType = provable(undefined); + const stateType = provable(null); let { actionState } = this.reduce( actionLists, stateType, (_, action) => { callback(action); - return undefined; + return null; }, - { state: undefined, actionState: fromActionState }, + { state: null, actionState: fromActionState }, config ); return actionState; @@ -1480,13 +1410,6 @@ Use the optional \`maxTransactionsWithActions\` argument to increase this number }; } -class VerificationKey extends Struct({ - ...provable({ data: String, hash: Field }), - toJSON({ data }: { data: string }) { - return data; - }, -}) {} - function selfAccountUpdate(zkapp: SmartContract, methodName?: string) { let body = Body.keepAll(zkapp.address, zkapp.tokenId); let update = new (AccountUpdate as any)(body, {}, true) as AccountUpdate; @@ -1502,11 +1425,20 @@ type ExecutionState = { accountUpdate: AccountUpdate; }; +const SmartContractContext = { + enter(self: SmartContract, selfUpdate: AccountUpdate) { + let context: SmartContractContext = { + this: self, + selfUpdate, + selfLayout: new AccountUpdateLayout(selfUpdate), + }; + let id = smartContractContext.enter(context); + return { id, context }; + }, +}; + type DeployArgs = - | { - verificationKey?: { data: string; hash: string | Field }; - zkappKey?: PrivateKey; - } + | { verificationKey?: { data: string; hash: string | Field } } | undefined; function Account(address: PublicKey, tokenId?: Field) { @@ -1544,7 +1476,7 @@ function declareMethods( let target = SmartContract.prototype; Reflect.metadata('design:paramtypes', argumentTypes)(target, key); let descriptor = Object.getOwnPropertyDescriptor(target, key)!; - method(SmartContract.prototype, key as any, descriptor); + method(SmartContract.prototype as any, key as any, descriptor); Object.defineProperty(target, key, descriptor); } } @@ -1566,6 +1498,59 @@ const Reducer: (< { get: Actions.emptyActionState } ) as any; +const ProofAuthorization = { + setKind( + { body, id }: AccountUpdate, + priorAccountUpdates?: AccountUpdateLayout + ) { + body.authorizationKind.isSigned = Bool(false); + body.authorizationKind.isProved = Bool(true); + let hash = Provable.witness(Field, () => { + let proverData = zkAppProver.getData(); + let isProver = proverData !== undefined; + assert( + isProver || priorAccountUpdates !== undefined, + 'Called `setKind()` outside the prover without passing in `priorAccountUpdates`.' + ); + let myAccountUpdateId = isProver ? proverData.accountUpdate.id : id; + let priorAccountUpdatesFlat = priorAccountUpdates?.toFlatList({ + mutate: false, + }); + priorAccountUpdatesFlat ??= proverData.transaction.accountUpdates; + priorAccountUpdatesFlat = priorAccountUpdatesFlat.filter( + (a) => a.id !== myAccountUpdateId + ); + let accountUpdate = [...priorAccountUpdatesFlat] + .reverse() + .find((body_) => + body_.update.verificationKey.isSome + .and(body_.tokenId.equals(body.tokenId)) + .and(body_.publicKey.equals(body.publicKey)) + .toBoolean() + ); + if (accountUpdate !== undefined) { + return accountUpdate.body.update.verificationKey.value.hash; + } + try { + let account = Mina.getAccount(body.publicKey, body.tokenId); + return account.zkapp?.verificationKey?.hash ?? Field(0); + } catch { + return Field(0); + } + }); + body.authorizationKind.verificationKeyHash = hash; + }, + setLazyProof( + accountUpdate: AccountUpdate, + proof: Omit, + priorAccountUpdates: AccountUpdateLayout + ) { + this.setKind(accountUpdate, priorAccountUpdates); + accountUpdate.authorization = {}; + accountUpdate.lazyAuthorization = { ...proof, kind: 'lazy-proof' }; + }, +}; + /** * this is useful to debug a very common error: when the consistency check between * -) the account update that went into the public input, and @@ -1577,3 +1562,57 @@ const Reducer: (< * TODO find or write library that can print nice JS object diffs */ const DEBUG_PUBLIC_INPUT_CHECK = false; + +function debugPublicInput(accountUpdate: AccountUpdate) { + if (!DEBUG_PUBLIC_INPUT_CHECK) return; + + // connect the public input to the account update & child account updates we created + Provable.asProver(() => { + diffRecursive(accountUpdate, zkAppProver.getData()); + }); +} + +function diffRecursive( + prover: AccountUpdate, + inputData: { + transaction: ZkappCommand; + index: number; + accountUpdate: AccountUpdate; + } +) { + let { transaction, index, accountUpdate: input } = inputData; + diff(transaction, index, prover.toPretty(), input.toPretty()); + // TODO + let inputChildren = accountUpdateLayout()!.get(input)!.children.mutable!; + let proverChildren = accountUpdateLayout()!.get(prover)!.children.mutable!; + let nChildren = inputChildren.length; + for (let i = 0; i < nChildren; i++) { + let inputChild = inputChildren[i].mutable; + let child = proverChildren[i].mutable; + if (!inputChild || !child) return; + diffRecursive(child, { transaction, index, accountUpdate: inputChild }); + } +} + +// TODO: print a nice diff string instead of the two objects +// something like `expect` or `json-diff`, but web-compatible +function diff( + transaction: ZkappCommand, + index: number, + prover: any, + input: any +) { + delete prover.id; + delete prover.callDepth; + delete input.id; + delete input.callDepth; + if (JSON.stringify(prover) !== JSON.stringify(input)) { + console.log('transaction:', ZkappCommand.toPretty(transaction)); + console.log('index', index); + console.log('inconsistent account updates:'); + console.log('update created by the prover:'); + console.log(prover); + console.log('update created in transaction block:'); + console.log(input); + } +} diff --git a/src/lib/ml/base.ts b/src/lib/ml/base.ts index 89de7b01bc..33cdb00975 100644 --- a/src/lib/ml/base.ts +++ b/src/lib/ml/base.ts @@ -1,21 +1,37 @@ +import { TupleN } from '../util/types.js'; + /** * This module contains basic methods for interacting with OCaml */ -export { MlArray, MlTuple, MlList, MlOption, MlBool, MlBytes }; +export { + MlArray, + MlPair, + MlList, + MlOption, + MlBool, + MlBytes, + MlResult, + MlUnit, + MlString, + MlTuple, +}; // ocaml types -type MlTuple = [0, X, Y]; +type MlPair = [0, X, Y]; type MlArray = [0, ...T[]]; type MlList = [0, T, 0 | MlList]; type MlOption = 0 | [0, T]; type MlBool = 0 | 1; +type MlResult = [0, T] | [1, E]; +type MlUnit = 0; /** * js_of_ocaml representation of a byte array, * see https://github.com/ocsigen/js_of_ocaml/blob/master/runtime/mlBytes.js */ type MlBytes = { t: number; c: string; l: number }; +type MlString = MlBytes; const MlArray = { to(arr: T[]): MlArray { @@ -24,20 +40,29 @@ const MlArray = { from([, ...arr]: MlArray): T[] { return arr; }, + map([, ...arr]: MlArray, map: (t: T) => S): MlArray { + return [0, ...arr.map(map)]; + }, + mapTo(arr: T[], map: (t: T) => S): MlArray { + return [0, ...arr.map(map)]; + }, + mapFrom([, ...arr]: MlArray, map: (t: T) => S): S[] { + return arr.map(map); + }, }; -const MlTuple = Object.assign( - function MlTuple(x: X, y: Y): MlTuple { +const MlPair = Object.assign( + function MlTuple(x: X, y: Y): MlPair { return [0, x, y]; }, { - from([, x, y]: MlTuple): [X, Y] { + from([, x, y]: MlPair): [X, Y] { return [x, y]; }, - first(t: MlTuple): X { + first(t: MlPair): X { return t[1]; }, - second(t: MlTuple): Y { + second(t: MlPair): Y { return t[2]; }, } @@ -53,3 +78,79 @@ const MlBool = Object.assign( }, } ); + +const MlOption = Object.assign( + function MlOption(x?: T): MlOption { + return x === undefined ? 0 : [0, x]; + }, + { + from(option: MlOption): T | undefined { + return option === 0 ? undefined : option[1]; + }, + map(option: MlOption, map: (t: T) => S): MlOption { + if (option === 0) return 0; + return [0, map(option[1])]; + }, + mapFrom(option: MlOption, map: (t: T) => S): S | undefined { + if (option === 0) return undefined; + return map(option[1]); + }, + mapTo(option: T | undefined, map: (t: T) => S): MlOption { + if (option === undefined) return 0; + return [0, map(option)]; + }, + isNone(option: MlOption): option is 0 { + return option === 0; + }, + isSome(option: MlOption): option is [0, T] { + return option !== 0; + }, + } +); + +const MlResult = { + ok(t: T): MlResult { + return [0, t]; + }, + unitError(): MlResult { + return [1, 0]; + }, +}; + +/** + * tuple type that has the length as generic parameter + */ +type MlTuple = N extends N + ? number extends N + ? [0, ...T[]] // N is not typed as a constant => fall back to array + : [0, ...TupleRec] + : never; + +type TupleRec = R['length'] extends N + ? R + : TupleRec; + +type Tuple = [T, ...T[]] | []; + +const MlTuple = { + map, B>( + [, ...mlTuple]: [0, ...T], + f: (a: T[number]) => B + ): [0, ...{ [i in keyof T]: B }] { + return [0, ...mlTuple.map(f)] as any; + }, + + mapFrom( + [, ...mlTuple]: MlTuple, + f: (a: T) => B + ): B[] { + return mlTuple.map(f); + }, + + mapTo | TupleN, B>( + tuple: T, + f: (a: T[number]) => B + ): [0, ...{ [i in keyof T]: B }] { + return [0, ...tuple.map(f)] as any; + }, +}; diff --git a/src/lib/ml/consistency.unit-test.ts b/src/lib/ml/consistency.unit-test.ts index d0b78d0a03..84a30f691e 100644 --- a/src/lib/ml/consistency.unit-test.ts +++ b/src/lib/ml/consistency.unit-test.ts @@ -1,12 +1,13 @@ -import { Ledger, Test } from '../../snarky.js'; +import { Test } from '../../snarky.js'; import { Random, test } from '../testing/property.js'; -import { Field, Bool } from '../core.js'; -import { PrivateKey, PublicKey } from '../signature.js'; -import { TokenId, dummySignature } from '../account_update.js'; +import { Field, Bool } from '../provable/wrapped.js'; +import { PrivateKey, PublicKey } from '../provable/crypto/signature.js'; +import { TokenId, dummySignature } from '../mina/account-update.js'; import { Ml } from './conversion.js'; import { expect } from 'expect'; -import { FieldConst } from '../field.js'; -import { Provable } from '../provable.js'; +import { FieldConst } from '../provable/core/fieldvar.js'; +import { Provable } from '../provable/provable.js'; +import { runAndCheckSync } from '../provable/core/provable-context.js'; // PrivateKey.toBase58, fromBase58 @@ -94,7 +95,7 @@ test(Random.publicKey, randomTokenId, (publicKey, field) => { }); let parentTokenId = Field(field); - Provable.runAndCheck(() => { + runAndCheckSync(() => { tokenOwner = Provable.witness(PublicKey, () => tokenOwner); parentTokenId = Provable.witness(Field, () => parentTokenId); diff --git a/src/lib/ml/conversion.ts b/src/lib/ml/conversion.ts index 646deac480..91b938d105 100644 --- a/src/lib/ml/conversion.ts +++ b/src/lib/ml/conversion.ts @@ -3,12 +3,12 @@ */ import type { MlPublicKey, MlPublicKeyVar } from '../../snarky.js'; -import { HashInput } from '../circuit_value.js'; -import { Bool, Field } from '../core.js'; -import { FieldConst, FieldVar } from '../field.js'; -import { Scalar, ScalarConst } from '../scalar.js'; -import { PrivateKey, PublicKey } from '../signature.js'; -import { MlTuple, MlBool, MlArray } from './base.js'; +import { HashInput } from '../provable/types/struct.js'; +import { Bool, Field } from '../provable/wrapped.js'; +import { FieldVar, FieldConst } from '../provable/core/fieldvar.js'; +import { Scalar, ScalarConst } from '../provable/scalar.js'; +import { PrivateKey, PublicKey } from '../provable/crypto/signature.js'; +import { MlPair, MlBool, MlArray } from './base.js'; import { MlFieldConstArray } from './fields.js'; export { Ml, MlHashInput }; @@ -35,7 +35,7 @@ const Ml = { type MlHashInput = [ flag: 0, field_elements: MlArray, - packed: MlArray> + packed: MlArray> ]; const MlHashInput = { @@ -86,7 +86,7 @@ function toPrivateKey(sk: ScalarConst) { } function fromPublicKey(pk: PublicKey): MlPublicKey { - return MlTuple(pk.x.toConstant().value[1], MlBool(pk.isOdd.toBoolean())); + return MlPair(pk.x.toConstant().value[1], MlBool(pk.isOdd.toBoolean())); } function toPublicKey([, x, isOdd]: MlPublicKey): PublicKey { return PublicKey.from({ @@ -96,12 +96,8 @@ function toPublicKey([, x, isOdd]: MlPublicKey): PublicKey { } function fromPublicKeyVar(pk: PublicKey): MlPublicKeyVar { - return MlTuple(pk.x.value, pk.isOdd.toField().value); + return MlPair(pk.x.value, pk.isOdd.toField().value); } function toPublicKeyVar([, x, isOdd]: MlPublicKeyVar): PublicKey { - return PublicKey.from({ - x: Field(x), - // TODO - isOdd: Bool.Unsafe.ofField(Field(isOdd)), - }); + return PublicKey.from({ x: Field(x), isOdd: Bool(isOdd) }); } diff --git a/src/lib/ml/fields.ts b/src/lib/ml/fields.ts index 4921e9272a..b9b70006ab 100644 --- a/src/lib/ml/fields.ts +++ b/src/lib/ml/fields.ts @@ -1,4 +1,5 @@ -import { ConstantField, Field, FieldConst, FieldVar } from '../field.js'; +import { ConstantField, Field } from '../provable/field.js'; +import { FieldVar, FieldConst } from '../provable/core/fieldvar.js'; import { MlArray } from './base.js'; export { MlFieldArray, MlFieldConstArray }; diff --git a/src/lib/proof-system/cache.ts b/src/lib/proof-system/cache.ts new file mode 100644 index 0000000000..42343c88fc --- /dev/null +++ b/src/lib/proof-system/cache.ts @@ -0,0 +1,222 @@ +import { + writeFileSync, + readFileSync, + mkdirSync, + resolve, + cacheDir, +} from '../util/fs.js'; +import { jsEnvironment } from '../../bindings/crypto/bindings/env.js'; + +// external API +export { Cache, CacheHeader }; + +// internal API +export { readCache, writeCache, withVersion, cacheHeaderVersion }; + +/** + * Interface for storing and retrieving values, for caching. + * `read()` and `write()` can just throw errors on failure. + * + * The data that will be passed to the cache for writing is exhaustively described by the {@link CacheHeader} type. + * It represents one of the following: + * - The SRS. This is a deterministic lists of curve points (one per curve) that needs to be generated just once, + * to be used for polynomial commitments. + * - Lagrange basis commitments. Similar to the SRS, this will be created once for every power-of-2 circuit size. + * - Prover and verifier keys for every compiled circuit. + * + * Per smart contract or ZkProgram, several different keys are created: + * - a step prover key (`step-pk`) and verification key (`step-vk`) _for every method_. + * - a wrap prover key (`wrap-pk`) and verification key (`wrap-vk`) for the entire contract. + */ +type Cache = { + /** + * Read a value from the cache. + * + * @param header A small header to identify what is read from the cache. + */ + read(header: CacheHeader): Uint8Array | undefined; + + /** + * Write a value to the cache. + * + * @param header A small header to identify what is written to the cache. This will be used by `read()` to retrieve the data. + * @param value The value to write to the cache, as a byte array. + */ + write(header: CacheHeader, value: Uint8Array): void; + + /** + * Indicates whether the cache is writable. + */ + canWrite: boolean; + + /** + * If `debug` is toggled, `read()` and `write()` errors are logged to the console. + * + * By default, cache errors are silent, because they don't necessarily represent an error condition, + * but could just be a cache miss, or file system permissions incompatible with writing data. + */ + debug?: boolean; +}; + +const cacheHeaderVersion = 1; + +type CommonHeader = { + /** + * Header version to avoid parsing incompatible headers. + */ + version: number; + /** + * An identifier that is persistent even as versions of the data change. Safe to use as a file path. + */ + persistentId: string; + /** + * A unique identifier for the data to be read. Safe to use as a file path. + */ + uniqueId: string; + /** + * Specifies whether the data to be read is a utf8-encoded string or raw binary data. This was added + * because node's `fs.readFileSync` returns garbage when reading string files without specifying the encoding. + */ + dataType: 'string' | 'bytes'; +}; + +type StepKeyHeader = { + kind: Kind; + programName: string; + methodName: string; + methodIndex: number; + hash: string; +}; +type WrapKeyHeader = { kind: Kind; programName: string; hash: string }; +type PlainHeader = { kind: Kind }; + +/** + * A header that is passed to the caching layer, to support rich caching strategies. + * + * Both `uniqueId` and `programId` can safely be used as a file path. + */ +type CacheHeader = ( + | StepKeyHeader<'step-pk'> + | StepKeyHeader<'step-vk'> + | WrapKeyHeader<'wrap-pk'> + | WrapKeyHeader<'wrap-vk'> + | PlainHeader<'srs'> + | PlainHeader<'lagrange-basis'> +) & + CommonHeader; + +function withVersion( + header: Omit, + version = cacheHeaderVersion +): CacheHeader { + let uniqueId = `${header.uniqueId}-${version}`; + return { ...header, version, uniqueId } as CacheHeader; +} + +// default methods to interact with a cache + +function readCache(cache: Cache, header: CacheHeader): Uint8Array | undefined; +function readCache( + cache: Cache, + header: CacheHeader, + transform: (x: Uint8Array) => T +): T | undefined; +function readCache( + cache: Cache, + header: CacheHeader, + transform?: (x: Uint8Array) => T +): T | undefined { + try { + let result = cache.read(header); + if (result === undefined) { + if (cache.debug) console.trace('cache miss'); + return undefined; + } + if (transform === undefined) return result as any as T; + return transform(result); + } catch (e) { + if (cache.debug) console.log('Failed to read cache', e); + return undefined; + } +} + +function writeCache(cache: Cache, header: CacheHeader, value: Uint8Array) { + if (!cache.canWrite) return false; + try { + cache.write(header, value); + return true; + } catch (e) { + if (cache.debug) console.log('Failed to write cache', e); + return false; + } +} + +const None: Cache = { + read() { + throw Error('not available'); + }, + write() { + throw Error('not available'); + }, + canWrite: false, +}; + +const FileSystem = (cacheDirectory: string, debug?: boolean): Cache => ({ + read({ persistentId, uniqueId, dataType }) { + if (jsEnvironment !== 'node') throw Error('file system not available'); + + // read current uniqueId, return data if it matches + let currentId = readFileSync( + resolve(cacheDirectory, `${persistentId}.header`), + 'utf8' + ); + if (currentId !== uniqueId) return undefined; + + if (dataType === 'string') { + let string = readFileSync(resolve(cacheDirectory, persistentId), 'utf8'); + return new TextEncoder().encode(string); + } else { + let buffer = readFileSync(resolve(cacheDirectory, persistentId)); + return new Uint8Array(buffer.buffer); + } + }, + write({ persistentId, uniqueId, dataType }, data) { + if (jsEnvironment !== 'node') throw Error('file system not available'); + mkdirSync(cacheDirectory, { recursive: true }); + writeFileSync(resolve(cacheDirectory, `${persistentId}.header`), uniqueId, { + encoding: 'utf8', + }); + writeFileSync(resolve(cacheDirectory, persistentId), data, { + encoding: dataType === 'string' ? 'utf8' : undefined, + }); + }, + canWrite: jsEnvironment === 'node', + debug, +}); + +const FileSystemDefault = FileSystem(cacheDir('o1js')); + +const Cache = { + /** + * Store data on the file system, in a directory of your choice. + * + * Data will be stored in two files per cache entry: a data file and a `.header` file. + * The header file just contains a unique string which is used to determine whether we can use the cached data. + * + * Note: this {@link Cache} only caches data in Node.js. + */ + FileSystem, + /** + * Store data on the file system, in a standard cache directory depending on the OS. + * + * Data will be stored in two files per cache entry: a data file and a `.header` file. + * The header file just contains a unique string which is used to determine whether we can use the cached data. + * + * Note: this {@link Cache} only caches data in Node.js. + */ + FileSystemDefault, + /** + * Don't store anything. + */ + None, +}; diff --git a/src/lib/circuit.ts b/src/lib/proof-system/circuit.ts similarity index 74% rename from src/lib/circuit.ts rename to src/lib/proof-system/circuit.ts index 232f3f4f0d..320e01b25b 100644 --- a/src/lib/circuit.ts +++ b/src/lib/proof-system/circuit.ts @@ -1,9 +1,17 @@ -import { ProvablePure, Snarky } from '../snarky.js'; -import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; -import { withThreadPool } from '../bindings/js/wrapper.js'; -import { Provable } from './provable.js'; -import { snarkContext, gatesFromJson } from './provable-context.js'; -import { prettifyStacktrace, prettifyStacktracePromise } from './errors.js'; +import 'reflect-metadata'; +import { Snarky } from '../../snarky.js'; +import { MlFieldArray, MlFieldConstArray } from '../ml/fields.js'; +import { withThreadPool } from '../../snarky.js'; +import { Provable } from '../provable/provable.js'; +import { + snarkContext, + gatesFromJson, +} from '../provable/core/provable-context.js'; +import { + prettifyStacktrace, + prettifyStacktracePromise, +} from '../util/errors.js'; +import { ProvablePure } from '../provable/types/provable-intf.js'; // external API export { public_, circuitMain, Circuit, Keypair, Proof, VerificationKey }; @@ -81,61 +89,6 @@ class Circuit { ) ); } - - // utility namespace, moved to `Provable` - - /** - * @deprecated use {@link Provable.witness} - */ - static witness = Provable.witness; - /** - * @deprecated use {@link Provable.asProver} - */ - static asProver = Provable.asProver; - /** - * @deprecated use {@link Provable.runAndCheck} - */ - static runAndCheck = Provable.runAndCheck; - /** - * @deprecated use {@link Provable.runUnchecked} - */ - static runUnchecked = Provable.runUnchecked; - /** - * @deprecated use {@link Provable.constraintSystem} - */ - static constraintSystem = Provable.constraintSystem; - /** - * @deprecated use {@link Provable.Array} - */ - static array = Provable.Array; - /** - * @deprecated use {@link Provable.assertEqual} - */ - static assertEqual = Provable.assertEqual; - /** - * @deprecated use {@link Provable.equal} - */ - static equal = Provable.equal; - /** - * @deprecated use {@link Provable.if} - */ - static if = Provable.if; - /** - * @deprecated use {@link Provable.switch} - */ - static switch = Provable.switch; - /** - * @deprecated use {@link Provable.inProver} - */ - static inProver = Provable.inProver; - /** - * @deprecated use {@link Provable.inCheckedComputation} - */ - static inCheckedComputation = Provable.inCheckedComputation; - /** - * @deprecated use {@link Provable.log} - */ - static log = Provable.log; } class Keypair { @@ -265,20 +218,27 @@ function circuitMain( }; } +type ProvableInputPure = ProvablePure | { provable: ProvablePure }; + // TODO support auxiliary data -function provableFromTuple(typs: ProvablePure[]): ProvablePure { +function provableFromTuple( + inputTypes: ProvableInputPure[] +): ProvablePure { + let types = inputTypes.map((t) => ('provable' in t ? t.provable : t)); return { sizeInFields: () => { - return typs.reduce((acc, typ) => acc + typ.sizeInFields(), 0); + return types.reduce((acc, type) => acc + type.sizeInFields(), 0); }, toFields: (t: Array) => { - if (t.length !== typs.length) { - throw new Error(`typOfArray: Expected ${typs.length}, got ${t.length}`); + if (t.length !== types.length) { + throw new Error( + `typOfArray: Expected ${types.length}, got ${t.length}` + ); } let res = []; for (let i = 0; i < t.length; ++i) { - res.push(...typs[i].toFields(t[i])); + res.push(...types[i].toFields(t[i])); } return res; }, @@ -290,7 +250,7 @@ function provableFromTuple(typs: ProvablePure[]): ProvablePure { fromFields: (xs: Array) => { let offset = 0; let res: Array = []; - typs.forEach((typ) => { + types.forEach((typ) => { const n = typ.sizeInFields(); res.push(typ.fromFields(xs.slice(offset, offset + n))); offset += n; @@ -299,7 +259,7 @@ function provableFromTuple(typs: ProvablePure[]): ProvablePure { }, check(xs: Array) { - typs.forEach((typ, i) => (typ as any).check(xs[i])); + types.forEach((typ, i) => (typ as any).check(xs[i])); }, }; } diff --git a/src/lib/proof-system/proof-system.unit-test.ts b/src/lib/proof-system/proof-system.unit-test.ts new file mode 100644 index 0000000000..b2e7501f94 --- /dev/null +++ b/src/lib/proof-system/proof-system.unit-test.ts @@ -0,0 +1,155 @@ +import { Field, Bool } from '../provable/wrapped.js'; +import { Struct } from '../provable/types/struct.js'; +import { UInt64 } from '../provable/int.js'; +import { + CompiledTag, + Empty, + Proof, + ZkProgram, + picklesRuleFromFunction, + sortMethodArguments, +} from './zkprogram.js'; +import { expect } from 'expect'; +import { Pickles, Snarky } from '../../snarky.js'; +import { AnyFunction } from '../util/types.js'; +import { snarkContext } from '../provable/core/provable-context.js'; +import { it } from 'node:test'; +import { Provable } from '../provable/provable.js'; +import { bool, equivalentAsync, field, record } from '../testing/equivalent.js'; +import { FieldVar, FieldConst } from '../provable/core/fieldvar.js'; +import { ProvablePure } from '../provable/types/provable-intf.js'; + +const EmptyProgram = ZkProgram({ + name: 'empty', + publicInput: Field, + methods: { run: { privateInputs: [], async method(_) {} } }, +}); + +class EmptyProof extends ZkProgram.Proof(EmptyProgram) {} + +// unit-test zkprogram creation helpers: +// -) sortMethodArguments +// -) picklesRuleFromFunction + +it('pickles rule creation', async () => { + // a rule that verifies a proof conditionally, and returns the proof's input as output + function main(proof: EmptyProof, shouldVerify: Bool) { + proof.verifyIf(shouldVerify); + return proof.publicInput; + } + let privateInputs = [EmptyProof, Bool]; + + // collect method interface + let methodIntf = sortMethodArguments('mock', 'main', privateInputs, Proof); + + expect(methodIntf).toEqual({ + methodName: 'main', + witnessArgs: [Bool], + proofArgs: [EmptyProof], + allArgs: [ + { type: 'proof', index: 0 }, + { type: 'witness', index: 0 }, + ], + }); + + // store compiled tag + CompiledTag.store(EmptyProgram, 'mock tag'); + + // create pickles rule + let rule: Pickles.Rule = picklesRuleFromFunction( + Empty as ProvablePure, + Field as ProvablePure, + main as AnyFunction, + { name: 'mock' }, + methodIntf, + [] + ); + + await equivalentAsync( + { from: [field, bool], to: record({ field, bool }) }, + { runs: 5 } + )( + (field, bool) => ({ field, bool }), + async (field, bool) => { + let dummy = await EmptyProof.dummy(field, undefined, 0); + let field_: FieldConst = [0, 0n]; + let bool_: FieldConst = [0, 0n]; + + await Provable.runAndCheck(async () => { + // put witnesses in snark context + snarkContext.get().witnesses = [dummy, bool]; + + // call pickles rule + let { + publicOutput: [, publicOutput], + shouldVerify: [, shouldVerify], + } = await rule.main([0]); + + // `publicOutput` and `shouldVerify` are as expected + Snarky.field.assertEqual(publicOutput, dummy.publicInput.value); + Snarky.field.assertEqual(shouldVerify, bool.value); + + Provable.asProver(() => { + field_ = Snarky.field.readVar(publicOutput); + bool_ = Snarky.field.readVar(shouldVerify); + }); + }); + + return { field: Field(field_), bool: Bool(FieldVar.constant(bool_)) }; + } + ); +}); + +// compile works with large inputs + +const N = 100_000; + +const program = ZkProgram({ + name: 'large-array-program', + methods: { + baseCase: { + privateInputs: [Provable.Array(Field, N)], + async method(_: Field[]) {}, + }, + }, +}); + +it('can compile program with large input', async () => { + await program.compile(); +}); + +// regression tests for some zkprograms +const emptyMethodsMetadata = await EmptyProgram.analyzeMethods(); +expect(emptyMethodsMetadata.run).toEqual( + expect.objectContaining({ + rows: 0, + digest: '4f5ddea76d29cfcfd8c595f14e31f21b', + gates: [], + publicInputSize: 0, + }) +); + +class CounterPublicInput extends Struct({ + current: UInt64, + updated: UInt64, +}) {} +const CounterProgram = ZkProgram({ + name: 'counter', + publicInput: CounterPublicInput, + methods: { + increment: { + privateInputs: [UInt64], + async method( + { current, updated }: CounterPublicInput, + incrementBy: UInt64 + ) { + const newCount = current.add(incrementBy); + newCount.assertEquals(updated); + }, + }, + }, +}); + +const incrementMethodMetadata = (await CounterProgram.analyzeMethods()) + .increment; +expect(incrementMethodMetadata).toEqual(expect.objectContaining({ rows: 18 })); diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts new file mode 100644 index 0000000000..7793532eb1 --- /dev/null +++ b/src/lib/proof-system/prover-keys.ts @@ -0,0 +1,257 @@ +/** + * This file provides helpers to + * - encode and decode all 4 kinds of snark keys to/from bytes + * - create a header which is passed to the `Cache` so that it can figure out where and if to read from cache + * + * The inputs are `SnarkKeyHeader` and `SnarkKey`, which are OCaml tagged enums defined in pickles_bindings.ml + */ +import { + WasmPastaFpPlonkIndex, + WasmPastaFqPlonkIndex, +} from '../../bindings/compiled/node_bindings/plonk_wasm.cjs'; +import { Pickles, wasm } from '../../snarky.js'; +import { VerifierIndex } from '../../bindings/crypto/bindings/kimchi-types.js'; +import { getRustConversion } from '../../bindings/crypto/bindings.js'; +import { MlString } from '../ml/base.js'; +import { CacheHeader, cacheHeaderVersion } from './cache.js'; +import type { MethodInterface } from './zkprogram.js'; + +export { + parseHeader, + encodeProverKey, + decodeProverKey, + SnarkKeyHeader, + SnarkKey, +}; +export type { MlWrapVerificationKey }; + +// there are 4 types of snark keys in Pickles which we all handle at once +enum KeyType { + StepProvingKey, + StepVerificationKey, + WrapProvingKey, + WrapVerificationKey, +} + +type SnarkKeyHeader = + | [KeyType.StepProvingKey, MlStepProvingKeyHeader] + | [KeyType.StepVerificationKey, MlStepVerificationKeyHeader] + | [KeyType.WrapProvingKey, MlWrapProvingKeyHeader] + | [KeyType.WrapVerificationKey, MlWrapVerificationKeyHeader]; + +type SnarkKey = + | [KeyType.StepProvingKey, MlBackendKeyPair] + | [KeyType.StepVerificationKey, VerifierIndex] + | [KeyType.WrapProvingKey, MlBackendKeyPair] + | [KeyType.WrapVerificationKey, MlWrapVerificationKey]; + +/** + * Create `CacheHeader` from a `SnarkKeyHeader` plus some context available to `compile()` + */ +function parseHeader( + programName: string, + methods: MethodInterface[], + header: SnarkKeyHeader +): CacheHeader { + let hash = Pickles.util.fromMlString(header[1][2][8]); + switch (header[0]) { + case KeyType.StepProvingKey: + case KeyType.StepVerificationKey: { + let kind = snarkKeyStringKind[header[0]]; + let methodIndex = header[1][3]; + let methodName = methods[methodIndex].methodName; + let persistentId = sanitize(`${kind}-${programName}-${methodName}`); + let uniqueId = sanitize( + `${kind}-${programName}-${methodIndex}-${methodName}-${hash}` + ); + return { + version: cacheHeaderVersion, + uniqueId, + kind, + persistentId, + programName, + methodName, + methodIndex, + hash, + dataType: snarkKeySerializationType[header[0]], + }; + } + case KeyType.WrapProvingKey: + case KeyType.WrapVerificationKey: { + let kind = snarkKeyStringKind[header[0]]; + let dataType = snarkKeySerializationType[header[0]]; + let persistentId = sanitize(`${kind}-${programName}`); + let uniqueId = sanitize(`${kind}-${programName}-${hash}`); + return { + version: cacheHeaderVersion, + uniqueId, + kind, + persistentId, + programName, + hash, + dataType, + }; + } + } +} + +/** + * Encode a snark key to bytes + */ +function encodeProverKey(value: SnarkKey): Uint8Array { + switch (value[0]) { + case KeyType.StepProvingKey: { + let index = value[1][1]; + let encoded = wasm.caml_pasta_fp_plonk_index_encode(index); + return encoded; + } + case KeyType.StepVerificationKey: { + let vkMl = value[1]; + const rustConversion = getRustConversion(wasm); + let vkWasm = rustConversion.fp.verifierIndexToRust(vkMl); + let string = wasm.caml_pasta_fp_plonk_verifier_index_serialize(vkWasm); + return new TextEncoder().encode(string); + } + case KeyType.WrapProvingKey: { + let index = value[1][1]; + let encoded = wasm.caml_pasta_fq_plonk_index_encode(index); + return encoded; + } + case KeyType.WrapVerificationKey: { + let vk = value[1]; + let string = Pickles.encodeVerificationKey(vk); + return new TextEncoder().encode(string); + } + default: + value satisfies never; + throw Error('unreachable'); + } +} + +/** + * Decode bytes to a snark key with the help of its header + */ +function decodeProverKey(header: SnarkKeyHeader, bytes: Uint8Array): SnarkKey { + switch (header[0]) { + case KeyType.StepProvingKey: { + let srs = Pickles.loadSrsFp(); + let index = wasm.caml_pasta_fp_plonk_index_decode(bytes, srs); + let cs = header[1][4]; + return [KeyType.StepProvingKey, [0, index, cs]]; + } + case KeyType.StepVerificationKey: { + let srs = Pickles.loadSrsFp(); + let string = new TextDecoder().decode(bytes); + let vkWasm = wasm.caml_pasta_fp_plonk_verifier_index_deserialize( + srs, + string + ); + const rustConversion = getRustConversion(wasm); + let vkMl = rustConversion.fp.verifierIndexFromRust(vkWasm); + return [KeyType.StepVerificationKey, vkMl]; + } + case KeyType.WrapProvingKey: { + let srs = Pickles.loadSrsFq(); + let index = wasm.caml_pasta_fq_plonk_index_decode(bytes, srs); + let cs = header[1][3]; + return [KeyType.WrapProvingKey, [0, index, cs]]; + } + case KeyType.WrapVerificationKey: { + let string = new TextDecoder().decode(bytes); + let vk = Pickles.decodeVerificationKey(string); + return [KeyType.WrapVerificationKey, vk]; + } + default: + header satisfies never; + throw Error('unreachable'); + } +} + +/** + * Sanitize a string so that it can be used as a file name + */ +function sanitize(string: string): string { + return string.toLowerCase().replace(/[^a-z0-9_-]/g, '_'); +} + +const snarkKeyStringKind = { + [KeyType.StepProvingKey]: 'step-pk', + [KeyType.StepVerificationKey]: 'step-vk', + [KeyType.WrapProvingKey]: 'wrap-pk', + [KeyType.WrapVerificationKey]: 'wrap-vk', +} as const; + +const snarkKeySerializationType = { + [KeyType.StepProvingKey]: 'bytes', + [KeyType.StepVerificationKey]: 'string', + [KeyType.WrapProvingKey]: 'bytes', + [KeyType.WrapVerificationKey]: 'string', +} as const; + +// pickles types + +// Plonk_constraint_system.Make()().t + +class MlConstraintSystem { + // opaque type +} + +// Dlog_plonk_based_keypair.Make().t + +type MlBackendKeyPair = [ + _: 0, + index: WasmIndex, + cs: MlConstraintSystem +]; + +// Snarky_keys_header.t + +type MlSnarkKeysHeader = [ + _: 0, + headerVersion: number, + kind: [_: 0, type: MlString, identifier: MlString], + constraintConstants: unknown, + commits: unknown, + length: number, + commitDate: MlString, + constraintSystemHash: MlString, + identifyingHash: MlString +]; + +// Pickles.Cache.{Step,Wrap}.Key.Proving.t + +type MlStepProvingKeyHeader = [ + _: 0, + typeEqual: number, + snarkKeysHeader: MlSnarkKeysHeader, + index: number, + constraintSystem: MlConstraintSystem +]; + +type MlStepVerificationKeyHeader = [ + _: 0, + typeEqual: number, + snarkKeysHeader: MlSnarkKeysHeader, + index: number, + digest: unknown +]; + +type MlWrapProvingKeyHeader = [ + _: 0, + typeEqual: number, + snarkKeysHeader: MlSnarkKeysHeader, + constraintSystem: MlConstraintSystem +]; + +type MlWrapVerificationKeyHeader = [ + _: 0, + typeEqual: number, + snarkKeysHeader: MlSnarkKeysHeader, + digest: unknown +]; + +// Pickles.Verification_key.t + +class MlWrapVerificationKey { + // opaque type +} diff --git a/src/lib/proof-system/workers.ts b/src/lib/proof-system/workers.ts new file mode 100644 index 0000000000..9076ad7554 --- /dev/null +++ b/src/lib/proof-system/workers.ts @@ -0,0 +1,17 @@ +export { workers, setNumberOfWorkers }; + +const workers = { + numWorkers: undefined as number | undefined, +}; + +/** + * Set the number of workers to use for parallelizing the proof generation. By default the number of workers is set to the number of physical CPU cores on your machine, but there may be some instances where you want to set the number of workers manually. Some machines may have a large number of cores, but not enough memory to support that many workers. In that case, you can set the number of workers to a lower number to avoid running out of memory. On the other hand, some machines with heterogeneous cores may benefit from setting the number of workers to a lower number to avoid contention between core types if load-link/store-conditional multithreading is used. Feel free to experiment and see what works best for your use case. Maybe you can squeeze slightly more performance out by tweaking this value :) + + * @example + * ```typescript + * setNumberOfWorkers(2); // set the number of workers to 2 + * ``` + */ +const setNumberOfWorkers = (numWorkers: number) => { + workers.numWorkers = numWorkers; +}; diff --git a/src/lib/proof_system.ts b/src/lib/proof-system/zkprogram.ts similarity index 69% rename from src/lib/proof_system.ts rename to src/lib/proof-system/zkprogram.ts index 36cb0e154f..246d3573cd 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -2,25 +2,43 @@ import { EmptyNull, EmptyUndefined, EmptyVoid, -} from '../bindings/lib/generic.js'; -import { withThreadPool } from '../bindings/js/wrapper.js'; -import { ProvablePure, Pickles } from '../snarky.js'; -import { Field, Bool } from './core.js'; +} from '../../bindings/lib/generic.js'; +import { withThreadPool } from '../../snarky.js'; +import { + Pickles, + FeatureFlags, + MlFeatureFlags, + Gate, + GateType, +} from '../../snarky.js'; +import { Field, Bool } from '../provable/wrapped.js'; import { FlexibleProvable, FlexibleProvablePure, InferProvable, ProvablePureExtended, + Struct, + provable, provablePure, - toConstant, -} from './circuit_value.js'; -import { Provable } from './provable.js'; -import { assert, prettifyStacktracePromise } from './errors.js'; -import { snarkContext } from './provable-context.js'; -import { hashConstant } from './hash.js'; -import { MlArray, MlTuple } from './ml/base.js'; -import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; -import { FieldConst, FieldVar } from './field.js'; +} from '../provable/types/struct.js'; +import { Provable } from '../provable/provable.js'; +import { assert, prettifyStacktracePromise } from '../util/errors.js'; +import { snarkContext } from '../provable/core/provable-context.js'; +import { hashConstant } from '../provable/crypto/poseidon.js'; +import { MlArray, MlBool, MlResult, MlPair } from '../ml/base.js'; +import { MlFieldArray, MlFieldConstArray } from '../ml/fields.js'; +import { FieldVar, FieldConst } from '../provable/core/fieldvar.js'; +import { Cache, readCache, writeCache } from './cache.js'; +import { + decodeProverKey, + encodeProverKey, + parseHeader, +} from './prover-keys.js'; +import { + setSrsCache, + unsetSrsCache, +} from '../../bindings/crypto/bindings/srs.js'; +import { ProvablePure } from '../provable/types/provable-intf.js'; // public API export { @@ -32,6 +50,7 @@ export { Empty, Undefined, Void, + VerificationKey, }; // internal API @@ -40,7 +59,6 @@ export { sortMethodArguments, getPreviousProofsForProver, MethodInterface, - GenericArgument, picklesRuleFromFunction, compileProgram, analyzeMethod, @@ -132,11 +150,47 @@ class Proof { this.proof = proof; // TODO optionally convert from string? this.maxProofsVerified = maxProofsVerified; } + + /** + * Dummy proof. This can be useful for ZkPrograms that handle the base case in the same + * method as the inductive case, using a pattern like this: + * + * ```ts + * method(proof: SelfProof, isRecursive: Bool) { + * proof.verifyIf(isRecursive); + * // ... + * } + * ``` + * + * To use such a method in the base case, you need a dummy proof: + * + * ```ts + * let dummy = await MyProof.dummy(publicInput, publicOutput, 1); + * await myProgram.myMethod(dummy, Bool(false)); + * ``` + * + * **Note**: The types of `publicInput` and `publicOutput`, as well as the `maxProofsVerified` parameter, + * must match your ZkProgram. `maxProofsVerified` is the maximum number of proofs that any of your methods take as arguments. + */ + static async dummy( + publicInput: Input, + publicOutput: OutPut, + maxProofsVerified: 0 | 1 | 2, + domainLog2: number = 14 + ): Promise> { + let dummyRaw = await dummyProof(maxProofsVerified, domainLog2); + return new this({ + publicInput, + publicOutput, + proof: dummyRaw, + maxProofsVerified, + }); + } } async function verify( proof: Proof | JsonProof, - verificationKey: string + verificationKey: string | VerificationKey ) { let picklesProof: Pickles.Proof; let statement: Pickles.Statement; @@ -152,19 +206,21 @@ async function verify( let output = MlFieldConstArray.to( (proof as JsonProof).publicOutput.map(Field) ); - statement = MlTuple(input, output); + statement = MlPair(input, output); } else { // proof class picklesProof = proof.proof; let type = getStatementType(proof.constructor as any); let input = toFieldConsts(type.input, proof.publicInput); let output = toFieldConsts(type.output, proof.publicOutput); - statement = MlTuple(input, output); + statement = MlPair(input, output); } + let vk = + typeof verificationKey === 'string' + ? verificationKey + : verificationKey.data; return prettifyStacktracePromise( - withThreadPool(() => - Pickles.verify(statement, picklesProof, verificationKey) - ) + withThreadPool(() => Pickles.verify(statement, picklesProof, vk)) ); } @@ -197,6 +253,7 @@ function ZkProgram< } >( config: StatementType & { + name: string; methods: { [I in keyof Types]: Method< InferProvableOrUndefined>, @@ -208,17 +265,35 @@ function ZkProgram< } ): { name: string; - compile: () => Promise<{ verificationKey: string }>; + compile: (options?: { cache?: Cache; forceRecompile?: boolean }) => Promise<{ + verificationKey: { data: string; hash: Field }; + }>; verify: ( proof: Proof< InferProvableOrUndefined>, InferProvableOrVoid> > ) => Promise; - digest: () => string; - analyzeMethods: () => ReturnType[]; + digest: () => Promise; + analyzeMethods: () => Promise<{ + [I in keyof Types]: UnwrapPromise>; + }>; publicInputType: ProvableOrUndefined>; publicOutputType: ProvableOrVoid>; + privateInputTypes: { + [I in keyof Types]: Method< + InferProvableOrUndefined>, + InferProvableOrVoid>, + Types[I] + >['privateInputs']; + }; + rawMethods: { + [I in keyof Types]: Method< + InferProvableOrUndefined>, + InferProvableOrVoid>, + Types[I] + >['method']; + }; } & { [I in keyof Types]: Prover< InferProvableOrUndefined>, @@ -227,10 +302,10 @@ function ZkProgram< >; } { let methods = config.methods; - let publicInputType: ProvablePure = config.publicInput! ?? Undefined; - let publicOutputType: ProvablePure = config.publicOutput! ?? Void; + let publicInputType: ProvablePure = config.publicInput ?? Undefined; + let publicOutputType: ProvablePure = config.publicOutput ?? Void; - let selfTag = { name: `Program${i++}` }; + let selfTag = { name: config.name }; type PublicInput = InferProvableOrUndefined< Get >; @@ -242,13 +317,32 @@ function ZkProgram< static tag = () => selfTag; } - let keys: (keyof Types & string)[] = Object.keys(methods).sort(); // need to have methods in (any) fixed order - let methodIntfs = keys.map((key) => + // TODO remove sort()! Object.keys() has a deterministic order + let methodKeys: (keyof Types & string)[] = Object.keys(methods).sort(); // need to have methods in (any) fixed order + let methodIntfs = methodKeys.map((key) => sortMethodArguments('program', key, methods[key].privateInputs, SelfProof) ); - let methodFunctions = keys.map((key) => methods[key].method); + let methodFunctions = methodKeys.map((key) => methods[key].method); let maxProofsVerified = getMaxProofsVerified(methodIntfs); + async function analyzeMethods() { + let methodsMeta: Record< + string, + UnwrapPromise> + > = {}; + for (let i = 0; i < methodIntfs.length; i++) { + let methodEntry = methodIntfs[i]; + methodsMeta[methodEntry.methodName] = await analyzeMethod( + publicInputType, + methodEntry, + methodFunctions[i] + ); + } + return methodsMeta as { + [I in keyof Types]: UnwrapPromise>; + }; + } + let compileOutput: | { provers: Pickles.Prover[]; @@ -259,17 +353,25 @@ function ZkProgram< } | undefined; - async function compile() { - let { provers, verify, verificationKey } = await compileProgram( + async function compile({ + cache = Cache.FileSystemDefault, + forceRecompile = false, + } = {}) { + let methodsMeta = await analyzeMethods(); + let gates = methodKeys.map((k) => methodsMeta[k].gates); + let { provers, verify, verificationKey } = await compileProgram({ publicInputType, publicOutputType, methodIntfs, - methodFunctions, - selfTag, - config.overrideWrapDomain - ); + methods: methodFunctions, + gates, + proofSystemTag: selfTag, + cache, + forceRecompile, + overrideWrapDomain: config.overrideWrapDomain, + }); compileOutput = { provers, verify }; - return { verificationKey: verificationKey.data }; + return { verificationKey }; } function toProver( @@ -299,7 +401,7 @@ function ZkProgram< } finally { snarkContext.leave(id); } - let [publicOutputFields, proof] = MlTuple.from(result); + let [publicOutputFields, proof] = MlPair.from(result); let publicOutput = fromFieldConsts(publicOutputType, publicOutputFields); class ProgramProof extends Proof { static publicInputType = publicInputType; @@ -325,7 +427,7 @@ function ZkProgram< } return [key, prove]; } - let provers = Object.fromEntries(keys.map(toProver)) as { + let provers = Object.fromEntries(methodKeys.map(toProver)) as { [I in keyof Types]: Prover; }; @@ -335,27 +437,19 @@ function ZkProgram< `Cannot verify proof, verification key not found. Try calling \`await program.compile()\` first.` ); } - let statement = MlTuple( + let statement = MlPair( toFieldConsts(publicInputType, proof.publicInput), toFieldConsts(publicOutputType, proof.publicOutput) ); return compileOutput.verify(statement, proof.proof); } - function digest() { - let methodData = methodIntfs.map((methodEntry, i) => - analyzeMethod(publicInputType, methodEntry, methodFunctions[i]) - ); - let hash = hashConstant( - Object.values(methodData).map((d) => Field(BigInt('0x' + d.digest))) - ); - return hash.toBigInt().toString(16); - } - - function analyzeMethods() { - return methodIntfs.map((methodEntry, i) => - analyzeMethod(publicInputType, methodEntry, methodFunctions[i]) + async function digest() { + let methodsMeta = await analyzeMethods(); + let digests: Field[] = methodKeys.map((k) => + Field(BigInt('0x' + methodsMeta[k].digest)) ); + return hashConstant(digests).toBigInt().toString(16); } return Object.assign( @@ -364,18 +458,34 @@ function ZkProgram< compile, verify, digest, + analyzeMethods, publicInputType: publicInputType as ProvableOrUndefined< Get >, publicOutputType: publicOutputType as ProvableOrVoid< Get >, - analyzeMethods, + privateInputTypes: Object.fromEntries( + methodKeys.map((key) => [key, methods[key].privateInputs]) + ) as any, + rawMethods: Object.fromEntries( + methodKeys.map((key) => [key, methods[key].method]) + ) as any, }, provers ); } +type ZkProgram< + S extends { + publicInput?: FlexibleProvablePure; + publicOutput?: FlexibleProvablePure; + }, + T extends { + [I in string]: Tuple; + } +> = ReturnType>; + let i = 0; class SelfProof extends Proof< @@ -383,6 +493,13 @@ class SelfProof extends Proof< PublicOutput > {} +class VerificationKey extends Struct({ + ...provable({ data: String, hash: Field }), + toJSON({ data }: { data: string }) { + return data; + }, +}) {} + function sortMethodArguments( programName: string, methodName: string, @@ -391,8 +508,7 @@ function sortMethodArguments( ): MethodInterface { let witnessArgs: Provable[] = []; let proofArgs: Subclass[] = []; - let allArgs: { type: 'proof' | 'witness' | 'generic'; index: number }[] = []; - let genericArgs: Subclass[] = []; + let allArgs: { type: 'proof' | 'witness'; index: number }[] = []; for (let i = 0; i < privateInputs.length; i++) { let privateInput = privateInputs[i]; if (isProof(privateInput)) { @@ -411,9 +527,9 @@ function sortMethodArguments( } else if (isAsFields(privateInput)) { allArgs.push({ type: 'witness', index: witnessArgs.length }); witnessArgs.push(privateInput); - } else if (isGeneric(privateInput)) { - allArgs.push({ type: 'generic', index: genericArgs.length }); - genericArgs.push(privateInput); + } else if (isAsFields((privateInput as any)?.provable)) { + allArgs.push({ type: 'witness', index: witnessArgs.length }); + witnessArgs.push((privateInput as any).provable); } else { throw Error( `Argument ${ @@ -428,13 +544,7 @@ function sortMethodArguments( `Suggestion: You can merge more than two proofs by merging two at a time in a binary tree.` ); } - return { - methodName, - witnessArgs, - proofArgs, - allArgs, - genericArgs, - }; + return { methodName, witnessArgs, proofArgs, allArgs }; } function isAsFields( @@ -456,22 +566,6 @@ function isProof(type: unknown): type is typeof Proof { ); } -class GenericArgument { - isEmpty: boolean; - constructor(isEmpty = false) { - this.isEmpty = isEmpty; - } -} -let emptyGeneric = () => new GenericArgument(true); - -function isGeneric(type: unknown): type is typeof GenericArgument { - // the second case covers subclasses - return ( - type === GenericArgument || - (typeof type === 'function' && type.prototype instanceof GenericArgument) - ); -} - function getPreviousProofsForProver( methodArgs: any[], { allArgs }: MethodInterface @@ -489,56 +583,100 @@ function getPreviousProofsForProver( type MethodInterface = { methodName: string; // TODO: unify types of arguments - // "circuit types" should be flexible enough to encompass proofs and callback arguments + // proofs should just be `Provable` as well witnessArgs: Provable[]; proofArgs: Subclass[]; - genericArgs: Subclass[]; - allArgs: { type: 'witness' | 'proof' | 'generic'; index: number }[]; + allArgs: { type: 'witness' | 'proof'; index: number }[]; returnType?: Provable; }; // reasonable default choice for `overrideWrapDomain` const maxProofsToWrapDomain = { 0: 0, 1: 1, 2: 1 } as const; -async function compileProgram( - publicInputType: ProvablePure, - publicOutputType: ProvablePure, - methodIntfs: MethodInterface[], - methods: ((...args: any) => void)[], - proofSystemTag: { name: string }, - overrideWrapDomain?: 0 | 1 | 2 -) { +async function compileProgram({ + publicInputType, + publicOutputType, + methodIntfs, + methods, + gates, + proofSystemTag, + cache, + forceRecompile, + overrideWrapDomain, +}: { + publicInputType: ProvablePure; + publicOutputType: ProvablePure; + methodIntfs: MethodInterface[]; + methods: ((...args: any) => unknown)[]; + gates: Gate[][]; + proofSystemTag: { name: string }; + cache: Cache; + forceRecompile: boolean; + overrideWrapDomain?: 0 | 1 | 2; +}) { let rules = methodIntfs.map((methodEntry, i) => picklesRuleFromFunction( publicInputType, publicOutputType, methods[i], proofSystemTag, - methodEntry + methodEntry, + gates[i] ) ); let maxProofs = getMaxProofsVerified(methodIntfs); overrideWrapDomain ??= maxProofsToWrapDomain[maxProofs]; + let picklesCache: Pickles.Cache = [ + 0, + function read_(mlHeader) { + if (forceRecompile) return MlResult.unitError(); + let header = parseHeader(proofSystemTag.name, methodIntfs, mlHeader); + let result = readCache(cache, header, (bytes) => + decodeProverKey(mlHeader, bytes) + ); + if (result === undefined) return MlResult.unitError(); + return MlResult.ok(result); + }, + function write_(mlHeader, value) { + if (!cache.canWrite) return MlResult.unitError(); + + let header = parseHeader(proofSystemTag.name, methodIntfs, mlHeader); + let didWrite = writeCache(cache, header, encodeProverKey(value)); + + if (!didWrite) return MlResult.unitError(); + return MlResult.ok(undefined); + }, + MlBool(cache.canWrite), + ]; + let { verificationKey, provers, verify, tag } = await prettifyStacktracePromise( withThreadPool(async () => { let result: ReturnType; let id = snarkContext.enter({ inCompile: true }); + setSrsCache(cache); try { result = Pickles.compile(MlArray.to(rules), { publicInputSize: publicInputType.sizeInFields(), publicOutputSize: publicOutputType.sizeInFields(), + storable: picklesCache, overrideWrapDomain, }); + let { getVerificationKey, provers, verify, tag } = result; + CompiledTag.store(proofSystemTag, tag); + let [, data, hash] = await getVerificationKey(); + let verificationKey = { data, hash: Field(hash) }; + return { + verificationKey, + provers: MlArray.from(provers), + verify, + tag, + }; } finally { snarkContext.leave(id); + unsetSrsCache(); } - let { getVerificationKey, provers, verify, tag } = result; - CompiledTag.store(proofSystemTag, tag); - let [, data, hash] = getVerificationKey(); - let verificationKey = { data, hash: Field(hash) }; - return { verificationKey, provers: MlArray.from(provers), verify, tag }; }) ); // wrap provers @@ -570,14 +708,15 @@ async function compileProgram( }; } -function analyzeMethod( +function analyzeMethod( publicInputType: ProvablePure, methodIntf: MethodInterface, - method: (...args: any) => T + method: (...args: any) => unknown ) { return Provable.constraintSystem(() => { let args = synthesizeMethodArguments(methodIntf, true); let publicInput = emptyWitness(publicInputType); + // note: returning the method result here makes this handle async methods if (publicInputType === Undefined || publicInputType === Void) return method(...args); return method(publicInput, ...args); @@ -587,11 +726,14 @@ function analyzeMethod( function picklesRuleFromFunction( publicInputType: ProvablePure, publicOutputType: ProvablePure, - func: (...args: unknown[]) => any, + func: (...args: unknown[]) => unknown, proofSystemTag: { name: string }, - { methodName, witnessArgs, proofArgs, allArgs }: MethodInterface + { methodName, witnessArgs, proofArgs, allArgs }: MethodInterface, + gates: Gate[] ): Pickles.Rule { - function main(publicInput: MlFieldArray): ReturnType { + async function main( + publicInput: MlFieldArray + ): ReturnType { let { witnesses: argsWithoutPublicInput, inProver } = snarkContext.get(); assert(!(inProver && argsWithoutPublicInput === undefined)); let finalArgs = []; @@ -601,9 +743,14 @@ function picklesRuleFromFunction( let arg = allArgs[i]; if (arg.type === 'witness') { let type = witnessArgs[arg.index]; - finalArgs[i] = Provable.witness(type, () => { - return argsWithoutPublicInput?.[i] ?? emptyValue(type); - }); + try { + finalArgs[i] = Provable.witness(type, () => { + return argsWithoutPublicInput?.[i] ?? emptyValue(type); + }); + } catch (e: any) { + e.message = `Error when witnessing in ${methodName}, argument ${i}: ${e.message}`; + throw e; + } } else if (arg.type === 'proof') { let Proof = proofArgs[arg.index]; let type = getStatementType(Proof); @@ -620,17 +767,15 @@ function picklesRuleFromFunction( proofs.push(proofInstance); let input = toFieldVars(type.input, publicInput); let output = toFieldVars(type.output, publicOutput); - previousStatements.push(MlTuple(input, output)); - } else if (arg.type === 'generic') { - finalArgs[i] = argsWithoutPublicInput?.[i] ?? emptyGeneric(); + previousStatements.push(MlPair(input, output)); } } let result: any; if (publicInputType === Undefined || publicInputType === Void) { - result = func(...finalArgs); + result = await func(...finalArgs); } else { let input = fromFieldVars(publicInputType, publicInput); - result = func(input, ...finalArgs); + result = await func(input, ...finalArgs); } // if the public output is empty, we don't evaluate `toFields(result)` to allow the function to return something else in that case let hasPublicOutput = publicOutputType.sizeInFields() !== 0; @@ -664,9 +809,13 @@ function picklesRuleFromFunction( return { isSelf: false, tag: compiledTag }; } }); + + let featureFlags = computeFeatureFlags(gates); + return { identifier: methodName, main, + featureFlags, proofsToVerify: MlArray.to(proofsToVerify), }; } @@ -686,8 +835,6 @@ function synthesizeMethodArguments( let publicInput = empty(type.input); let publicOutput = empty(type.output); args.push(new Proof({ publicInput, publicOutput, proof: undefined })); - } else if (arg.type === 'generic') { - args.push(emptyGeneric()); } } return args; @@ -702,17 +849,15 @@ function methodArgumentsToConstant( let arg = args[i]; let { type, index } = allArgs[i]; if (type === 'witness') { - constArgs.push(toConstant(witnessArgs[index], arg)); + constArgs.push(Provable.toConstant(witnessArgs[index], arg)); } else if (type === 'proof') { let Proof = proofArgs[index]; let type = getStatementType(Proof); - let publicInput = toConstant(type.input, arg.publicInput); - let publicOutput = toConstant(type.output, arg.publicOutput); + let publicInput = Provable.toConstant(type.input, arg.publicInput); + let publicOutput = Provable.toConstant(type.output, arg.publicOutput); constArgs.push( new Proof({ publicInput, publicOutput, proof: arg.proof }) ); - } else if (type === 'generic') { - constArgs.push(arg); } } return constArgs; @@ -740,8 +885,6 @@ function methodArgumentTypesAndValues( let type = provablePure({ input: types.input, output: types.output }); let value = { input: proof.publicInput, output: proof.publicOutput }; typesAndValues.push({ type, value }); - } else if (type === 'generic') { - typesAndValues.push({ type: Generic, value: arg }); } } return typesAndValues; @@ -818,8 +961,55 @@ ZkProgram.Proof = function < }; }; -function dummyBase64Proof() { - return withThreadPool(async () => Pickles.dummyBase64Proof()); +function dummyProof(maxProofsVerified: 0 | 1 | 2, domainLog2: number) { + return withThreadPool( + async () => Pickles.dummyProof(maxProofsVerified, domainLog2)[1] + ); +} + +async function dummyBase64Proof() { + let proof = await dummyProof(2, 15); + return Pickles.proofToBase64([2, proof]); +} + +// what feature flags to set to enable certain gate types + +const gateToFlag: Partial> = { + RangeCheck0: 'rangeCheck0', + RangeCheck1: 'rangeCheck1', + ForeignFieldAdd: 'foreignFieldAdd', + ForeignFieldMul: 'foreignFieldMul', + Xor16: 'xor', + Rot64: 'rot', + Lookup: 'lookup', +}; + +function computeFeatureFlags(gates: Gate[]): MlFeatureFlags { + let flags: FeatureFlags = { + rangeCheck0: false, + rangeCheck1: false, + foreignFieldAdd: false, + foreignFieldMul: false, + xor: false, + rot: false, + lookup: false, + runtimeTables: false, + }; + for (let gate of gates) { + let flag = gateToFlag[gate.type]; + if (flag !== undefined) flags[flag] = true; + } + return [ + 0, + MlBool(flags.rangeCheck0), + MlBool(flags.rangeCheck1), + MlBool(flags.foreignFieldAdd), + MlBool(flags.foreignFieldMul), + MlBool(flags.xor), + MlBool(flags.rot), + MlBool(flags.lookup), + MlBool(flags.runtimeTables), + ]; } // helpers for circuit context @@ -870,14 +1060,14 @@ type Method< > = PublicInput extends undefined ? { privateInputs: Args; - method(...args: TupleToInstances): PublicOutput; + method(...args: TupleToInstances): Promise; } : { privateInputs: Args; method( publicInput: PublicInput, ...args: TupleToInstances - ): PublicOutput; + ): Promise; }; type Prover< diff --git a/src/lib/proof_system.unit-test.ts b/src/lib/proof_system.unit-test.ts deleted file mode 100644 index e7ec592915..0000000000 --- a/src/lib/proof_system.unit-test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Field } from './core.js'; -import { Struct } from './circuit_value.js'; -import { UInt64 } from './int.js'; -import { ZkProgram } from './proof_system.js'; -import { expect } from 'expect'; - -const EmptyProgram = ZkProgram({ - publicInput: Field, - methods: { - run: { - privateInputs: [], - method: (publicInput: Field) => {}, - }, - }, -}); - -const emptyMethodsMetadata = EmptyProgram.analyzeMethods(); -emptyMethodsMetadata.forEach((methodMetadata) => { - expect(methodMetadata).toEqual({ - rows: 0, - digest: '4f5ddea76d29cfcfd8c595f14e31f21b', - result: undefined, - gates: [], - publicInputSize: 0, - }); -}); - -class CounterPublicInput extends Struct({ - current: UInt64, - updated: UInt64, -}) {} -const CounterProgram = ZkProgram({ - publicInput: CounterPublicInput, - methods: { - increment: { - privateInputs: [UInt64], - method: ( - { current, updated }: CounterPublicInput, - incrementBy: UInt64 - ) => { - const newCount = current.add(incrementBy); - newCount.assertEquals(updated); - }, - }, - }, -}); - -const incrementMethodMetadata = CounterProgram.analyzeMethods()[0]; -expect(incrementMethodMetadata).toEqual(expect.objectContaining({ rows: 18 })); diff --git a/src/lib/provable-context.ts b/src/lib/provable-context.ts deleted file mode 100644 index 05310af119..0000000000 --- a/src/lib/provable-context.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { Context } from './global-context.js'; -import { Gate, JsonGate, Snarky } from '../snarky.js'; -import { bytesToBigInt } from '../bindings/crypto/bigint-helpers.js'; -import { prettifyStacktrace } from './errors.js'; - -// internal API -export { - snarkContext, - SnarkContext, - asProver, - runAndCheck, - runUnchecked, - constraintSystem, - inProver, - inAnalyze, - inCheckedComputation, - inCompile, - inCompileMode, - gatesFromJson, -}; - -// global circuit-related context - -type SnarkContext = { - witnesses?: unknown[]; - proverData?: any; - inProver?: boolean; - inCompile?: boolean; - inCheckedComputation?: boolean; - inAnalyze?: boolean; - inRunAndCheck?: boolean; - inWitnessBlock?: boolean; -}; -let snarkContext = Context.create({ default: {} }); - -// helpers to read circuit context - -function inProver() { - return !!snarkContext.get().inProver; -} -function inCheckedComputation() { - let ctx = snarkContext.get(); - return !!ctx.inCompile || !!ctx.inProver || !!ctx.inCheckedComputation; -} -function inCompile() { - return !!snarkContext.get().inCompile; -} -function inAnalyze() { - return !!snarkContext.get().inAnalyze; -} - -function inCompileMode() { - let ctx = snarkContext.get(); - return !!ctx.inCompile || !!ctx.inAnalyze; -} - -// runners for provable code - -function asProver(f: () => void) { - if (inCheckedComputation()) { - Snarky.run.asProver(f); - } else { - f(); - } -} - -function runAndCheck(f: () => void) { - let id = snarkContext.enter({ inCheckedComputation: true }); - try { - Snarky.run.runAndCheck(f); - } catch (error) { - throw prettifyStacktrace(error); - } finally { - snarkContext.leave(id); - } -} - -function runUnchecked(f: () => void) { - let id = snarkContext.enter({ inCheckedComputation: true }); - try { - Snarky.run.runUnchecked(f); - } catch (error) { - throw prettifyStacktrace(error); - } finally { - snarkContext.leave(id); - } -} - -function constraintSystem(f: () => T) { - let id = snarkContext.enter({ inAnalyze: true, inCheckedComputation: true }); - try { - let result: T; - let { rows, digest, json } = Snarky.run.constraintSystem(() => { - result = f(); - }); - let { gates, publicInputSize } = gatesFromJson(json); - return { rows, digest, result: result! as T, gates, publicInputSize }; - } catch (error) { - throw prettifyStacktrace(error); - } finally { - snarkContext.leave(id); - } -} - -// helpers - -function gatesFromJson(cs: { gates: JsonGate[]; public_input_size: number }) { - let gates: Gate[] = cs.gates.map(({ typ, wires, coeffs: byteCoeffs }) => { - let coeffs = []; - for (let coefficient of byteCoeffs) { - let arr = new Uint8Array(coefficient); - coeffs.push(bytesToBigInt(arr).toString()); - } - return { type: typ, wires, coeffs }; - }); - return { publicInputSize: cs.public_input_size, gates }; -} diff --git a/src/lib/bool.ts b/src/lib/provable/bool.ts similarity index 78% rename from src/lib/bool.ts rename to src/lib/provable/bool.ts index 387a4908da..8e6c7b4f9f 100644 --- a/src/lib/bool.ts +++ b/src/lib/provable/bool.ts @@ -1,17 +1,14 @@ -import { Snarky } from '../snarky.js'; -import { - Field, - FieldConst, - FieldType, - FieldVar, - readVarMessage, -} from './field.js'; -import { Bool as B } from '../provable/field-bigint.js'; -import { defineBinable } from '../bindings/lib/binable.js'; -import { NonNegativeInteger } from '../bindings/crypto/non-negative.js'; -import { asProver } from './provable-context.js'; - -export { BoolVar, Bool, isBool }; +import { Snarky } from '../../snarky.js'; +import { Field, readVarMessage, withMessage } from './field.js'; +import { FieldVar, FieldConst, FieldType } from './core/fieldvar.js'; +import { defineBinable } from '../../bindings/lib/binable.js'; +import { NonNegativeInteger } from '../../bindings/crypto/non-negative.js'; +import { asProver } from './core/provable-context.js'; +import { existsOne } from './core/exists.js'; +import { assertMul } from './gadgets/compatible.js'; +import { setBoolConstructor } from './core/field-constructor.js'; + +export { BoolVar, Bool }; // same representation, but use a different name to communicate intent / constraints type BoolVar = FieldVar; @@ -34,7 +31,7 @@ class Bool { value: BoolVar; constructor(x: boolean | Bool | BoolVar) { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { this.value = x.value; return; } @@ -42,7 +39,7 @@ class Bool { this.value = x; return; } - this.value = FieldVar.constant(B(x)); + this.value = FieldVar.constant(BigInt(x)); } isConstant(): this is { value: ConstantBoolVar } { @@ -63,7 +60,9 @@ class Bool { if (this.isConstant()) { return new Bool(!this.toBoolean()); } - return new Bool(Snarky.bool.not(this.value)); + // 1 - x + let not = new Field(1).sub(this.toField()); + return new Bool(not.value); } /** @@ -75,7 +74,8 @@ class Bool { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBoolean() && toBoolean(y)); } - return new Bool(Snarky.bool.and(this.value, Bool.#toVar(y))); + // x * y + return new Bool(this.toField().mul(Bool.toField(y)).value); } /** @@ -87,7 +87,8 @@ class Bool { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBoolean() || toBoolean(y)); } - return new Bool(Snarky.bool.or(this.value, Bool.#toVar(y))); + // 1 - (1 - x)(1 - y) = x + y - xy + return this.not().and(new Bool(y).not()).not(); } /** @@ -102,7 +103,7 @@ class Bool { } return; } - Snarky.bool.assertEqual(this.value, Bool.#toVar(y)); + this.toField().assertEquals(Bool.toField(y)); } catch (err) { throw withMessage(err, message); } @@ -144,7 +145,22 @@ class Bool { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBoolean() === toBoolean(y)); } - return new Bool(Snarky.bool.equals(this.value, Bool.#toVar(y))); + if (isConstant(y)) { + if (toBoolean(y)) return this; + else return this.not(); + } + if (this.isConstant()) { + return new Bool(y).equals(this); + } + // 1 - (x - y)^2 = 2xy - x - y + 1 + // match snarky logic: + // 2x * y === x + y - z + // return 1 - z + let z = existsOne(() => BigInt(this.toBoolean() !== toBoolean(y))); + let x = this.toField(); + let y_ = Bool.toField(y); + assertMul(x.add(x), y_, x.add(y_).sub(z)); + return new Bool(z.value).not(); } /** @@ -178,7 +194,7 @@ class Bool { } /** - * This converts the {@link Bool} to a javascript [[boolean]]. + * This converts the {@link Bool} to a JS `boolean`. * This can only be called on non-witness values. */ toBoolean(): boolean { @@ -194,14 +210,14 @@ class Bool { } static toField(x: Bool | boolean): Field { - return new Field(Bool.#toVar(x)); + return new Field(toFieldVar(x)); } /** * Boolean negation. */ static not(x: Bool | boolean): Bool { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { return x.not(); } return new Bool(!x); @@ -211,7 +227,7 @@ class Bool { * Boolean AND operation. */ static and(x: Bool | boolean, y: Bool | boolean): Bool { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { return x.and(y); } return new Bool(x).and(y); @@ -221,7 +237,7 @@ class Bool { * Boolean OR operation. */ static or(x: Bool | boolean, y: Bool | boolean): Bool { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { return x.or(y); } return new Bool(x).or(y); @@ -231,7 +247,7 @@ class Bool { * Asserts if both {@link Bool} are equal. */ static assertEqual(x: Bool, y: Bool | boolean): void { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { x.assertEquals(y); return; } @@ -242,7 +258,7 @@ class Bool { * Checks two {@link Bool} for equality. */ static equal(x: Bool | boolean, y: Bool | boolean): Bool { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { return x.equals(y); } return new Bool(x).equals(y); @@ -295,6 +311,10 @@ class Bool { return 1; } + static empty() { + return new Bool(false); + } + static toInput(x: Bool): { packed: [Field, number][] } { return { packed: [[x.toField(), 1] as [Field, number]] }; } @@ -314,24 +334,22 @@ class Bool { return BoolBinable.readBytes(bytes, offset); } - static sizeInBytes() { - return 1; - } + static sizeInBytes = 1; static check(x: Bool): void { - Snarky.field.assertBoolean(x.value); + x.toField().assertBool(); } static Unsafe = { /** - * Converts a {@link Field} into a {@link Bool}. This is a **dangerous** operation + * Converts a {@link Field} into a {@link Bool}. This is an **unsafe** operation * as it assumes that the field element is either 0 or 1 (which might not be true). * - * Only use this with constants or if you have already constrained the Field element to be 0 or 1. + * Only use this if you have already constrained the Field element to be 0 or 1. * * @param x a {@link Field} */ - ofField(x: Field) { + fromField(x: Field) { asProver(() => { let x0 = x.toBigInt(); if (x0 !== 0n && x0 !== 1n) @@ -340,16 +358,8 @@ class Bool { return new Bool(x.value); }, }; - - static #isBool(x: boolean | Bool | BoolVar): x is Bool { - return x instanceof Bool; - } - - static #toVar(x: boolean | Bool): BoolVar { - if (Bool.#isBool(x)) return x.value; - return FieldVar.constant(B(x)); - } } +setBoolConstructor(Bool); const BoolBinable = defineBinable({ toBytes(b: Bool) { @@ -360,6 +370,8 @@ const BoolBinable = defineBinable({ }, }); +// internal helper functions + function isConstant(x: boolean | Bool): x is boolean | ConstantBool { if (typeof x === 'boolean') { return true; @@ -368,10 +380,6 @@ function isConstant(x: boolean | Bool): x is boolean | ConstantBool { return x.isConstant(); } -function isBool(x: unknown) { - return x instanceof Bool; -} - function toBoolean(x: boolean | Bool): boolean { if (typeof x === 'boolean') { return x; @@ -379,9 +387,7 @@ function toBoolean(x: boolean | Bool): boolean { return x.toBoolean(); } -// TODO: This is duplicated -function withMessage(error: unknown, message?: string) { - if (message === undefined || !(error instanceof Error)) return error; - error.message = `${message}\n${error.message}`; - return error; +function toFieldVar(x: boolean | Bool): BoolVar { + if (x instanceof Bool) return x.value; + return FieldVar.constant(BigInt(x)); } diff --git a/src/lib/provable/bytes.ts b/src/lib/provable/bytes.ts new file mode 100644 index 0000000000..e83b09fba0 --- /dev/null +++ b/src/lib/provable/bytes.ts @@ -0,0 +1,134 @@ +import { provableFromClass } from './types/provable-derivers.js'; +import type { ProvablePureExtended } from './types/struct.js'; +import { assert } from './gadgets/common.js'; +import { chunkString } from '../util/arrays.js'; +import { Provable } from './provable.js'; +import { UInt8 } from './int.js'; +import { randomBytes } from '../../bindings/crypto/random.js'; + +// external API +export { Bytes }; + +// internal API +export { createBytes, FlexibleBytes }; + +type FlexibleBytes = Bytes | (UInt8 | bigint | number)[] | Uint8Array; + +/** + * A provable type representing an array of bytes. + */ +class Bytes { + bytes: UInt8[]; + + constructor(bytes: UInt8[]) { + let size = (this.constructor as typeof Bytes).size; + + // assert that data is not too long + assert( + bytes.length <= size, + `Expected at most ${size} bytes, got ${bytes.length}` + ); + + // pad the data with zeros + let padding = Array.from( + { length: size - bytes.length }, + () => new UInt8(0) + ); + this.bytes = bytes.concat(padding); + } + + /** + * Coerce the input to {@link Bytes}. + * + * Inputs smaller than `this.size` are padded with zero bytes. + */ + static from(data: (UInt8 | bigint | number)[] | Uint8Array | Bytes): Bytes { + if (data instanceof Bytes) return data; + if (this._size === undefined) { + let Bytes_ = createBytes(data.length); + return Bytes_.from(data); + } + return new this([...data].map(UInt8.from)); + } + + toBytes(): Uint8Array { + return Uint8Array.from(this.bytes.map((x) => x.toNumber())); + } + + toFields() { + return this.bytes.map((x) => x.value); + } + + /** + * Create {@link Bytes} from a string. + * + * Inputs smaller than `this.size` are padded with zero bytes. + */ + static fromString(s: string) { + let bytes = new TextEncoder().encode(s); + return this.from(bytes); + } + + /** + * Create random {@link Bytes} using secure builtin randomness. + */ + static random() { + let bytes = randomBytes(this.size); + return this.from(bytes); + } + + /** + * Create {@link Bytes} from a hex string. + * + * Inputs smaller than `this.size` are padded with zero bytes. + */ + static fromHex(xs: string): Bytes { + let bytes = chunkString(xs, 2).map((s) => parseInt(s, 16)); + return this.from(bytes); + } + + /** + * Convert {@link Bytes} to a hex string. + */ + toHex(): string { + return this.bytes + .map((x) => x.toBigInt().toString(16).padStart(2, '0')) + .join(''); + } + + // dynamic subclassing infra + static _size?: number; + static _provable?: ProvablePureExtended< + Bytes, + { bytes: { value: string }[] } + >; + + /** + * The size of the {@link Bytes}. + */ + static get size() { + assert(this._size !== undefined, 'Bytes not initialized'); + return this._size; + } + + get length() { + return this.bytes.length; + } + + /** + * `Provable` + */ + static get provable() { + assert(this._provable !== undefined, 'Bytes not initialized'); + return this._provable; + } +} + +function createBytes(size: number): typeof Bytes { + return class Bytes_ extends Bytes { + static _size = size; + static _provable = provableFromClass(Bytes_, { + bytes: Provable.Array(UInt8, size), + }); + }; +} diff --git a/src/lib/provable/core/exists.ts b/src/lib/provable/core/exists.ts new file mode 100644 index 0000000000..f4f0a2fac2 --- /dev/null +++ b/src/lib/provable/core/exists.ts @@ -0,0 +1,90 @@ +import { Snarky } from '../../../snarky.js'; +import { FieldConst, VarFieldVar } from './fieldvar.js'; +import type { VarField } from '../field.js'; +import { MlArray, MlOption } from '../../ml/base.js'; +import { createField } from './field-constructor.js'; +import { TupleN } from '../../util/types.js'; + +export { createVarField, exists, existsAsync, existsOne }; + +/** + * Witness `size` field element variables by passing a callback that returns `size` bigints. + * + * Note: this is called "exists" because in a proof, you use it like this: + * > "I prove that there exists x, such that (some statement)" + */ +function exists TupleN>( + size: N, + compute: C +): TupleN { + // enter prover block + let finish = Snarky.run.enterAsProver(size); + + if (!Snarky.run.inProver()) { + // step outside prover block and create vars: compile case + let vars = MlArray.mapFrom(finish(MlOption()), createVarField); + return TupleN.fromArray(size, vars); + } + + // run the callback to get values to witness + let values = compute(); + if (values.length !== size) + throw Error( + `Expected witnessed values of length ${size}, got ${values.length}.` + ); + + // note: here, we deliberately reduce the bigint values modulo the field size + // this makes it easier to use normal arithmetic in low-level gadgets, + // i.e. you can just witness x - y and it will be a field subtraction + let inputValues = MlArray.mapTo(values, FieldConst.fromBigint); + + // step outside prover block and create vars: prover case + let fieldVars = finish(MlOption(inputValues)); + let vars = MlArray.mapFrom(fieldVars, createVarField); + return TupleN.fromArray(size, vars); +} + +/** + * Variant of {@link exists} that witnesses 1 field element. + */ +function existsOne(compute: () => bigint): VarField { + return exists(1, () => [compute()])[0]; +} + +/** + * Async variant of {@link exists}, which allows an async callback. + */ +async function existsAsync< + N extends number, + C extends () => Promise> +>(size: N, compute: C): Promise> { + // enter prover block + let finish = Snarky.run.enterAsProver(size); + + if (!Snarky.run.inProver()) { + let vars = MlArray.mapFrom(finish(MlOption()), createVarField); + return TupleN.fromArray(size, vars); + } + + // run the async callback to get values to witness + let values = await compute(); + if (values.length !== size) + throw Error( + `Expected witnessed values of length ${size}, got ${values.length}.` + ); + + // note: here, we deliberately reduce the bigint values modulo the field size + // this makes it easier to use normal arithmetic in low-level gadgets, + // i.e. you can just witness x - y and it will be a field subtraction + let inputValues = MlArray.mapTo(values, FieldConst.fromBigint); + + let fieldVars = finish(MlOption(inputValues)); + let vars = MlArray.mapFrom(fieldVars, createVarField); + return TupleN.fromArray(size, vars); +} + +// helpers for varfields + +function createVarField(x: VarFieldVar): VarField { + return createField(x) as VarField; +} diff --git a/src/lib/provable/core/field-constructor.ts b/src/lib/provable/core/field-constructor.ts new file mode 100644 index 0000000000..1b27e36b6d --- /dev/null +++ b/src/lib/provable/core/field-constructor.ts @@ -0,0 +1,75 @@ +/** + * Stub module to break dependency cycle between Field and Bool classes and + * core gadgets which they depend on but which need to create Fields and Bools, + * or check if a value is a Field or a Bool. + */ +import type { Field } from '../field.js'; +import type { Bool } from '../bool.js'; +import type { FieldVar, FieldConst } from './fieldvar.js'; + +export { + createField, + createBool, + createBoolUnsafe, + isField, + isBool, + getField, + getBool, + setFieldConstructor, + setBoolConstructor, +}; + +let fieldConstructor: typeof Field | undefined; +let boolConstructor: typeof Bool | undefined; + +function setFieldConstructor(constructor: typeof Field) { + fieldConstructor = constructor; +} +function setBoolConstructor(constructor: typeof Bool) { + boolConstructor = constructor; +} + +function createField( + value: string | number | bigint | Field | FieldVar | FieldConst +): Field { + if (fieldConstructor === undefined) + throw Error('Cannot construct a Field before the class was defined.'); + return new fieldConstructor(value); +} + +function createBool(value: boolean | Bool): Bool { + if (boolConstructor === undefined) + throw Error('Cannot construct a Bool before the class was defined.'); + return new boolConstructor(value); +} + +function createBoolUnsafe(value: Field): Bool { + return getBool().Unsafe.fromField(value); +} + +function isField(x: unknown): x is Field { + if (fieldConstructor === undefined) + throw Error( + 'Cannot check for instance of Field before the class was defined.' + ); + return x instanceof fieldConstructor; +} + +function isBool(x: unknown): x is Bool { + if (boolConstructor === undefined) + throw Error( + 'Cannot check for instance of Bool before the class was defined.' + ); + return x instanceof boolConstructor; +} + +function getField(): typeof Field { + if (fieldConstructor === undefined) + throw Error('Field class not defined yet.'); + return fieldConstructor; +} + +function getBool(): typeof Bool { + if (boolConstructor === undefined) throw Error('Bool class not defined yet.'); + return boolConstructor; +} diff --git a/src/lib/provable/core/fieldvar.ts b/src/lib/provable/core/fieldvar.ts new file mode 100644 index 0000000000..a65f4d93eb --- /dev/null +++ b/src/lib/provable/core/fieldvar.ts @@ -0,0 +1,116 @@ +import { Fp } from '../../../bindings/crypto/finite-field.js'; + +// internal API +export { FieldType, FieldVar, FieldConst, ConstantFieldVar, VarFieldVar }; + +type FieldConst = [0, bigint]; + +function constToBigint(x: FieldConst): bigint { + return x[1]; +} +function constFromBigint(x: bigint): FieldConst { + return [0, Fp.mod(x)]; +} + +const FieldConst = { + fromBigint: constFromBigint, + toBigint: constToBigint, + equal(x: FieldConst, y: FieldConst) { + return x[1] === y[1]; + }, + [0]: constFromBigint(0n), + [1]: constFromBigint(1n), + [-1]: constFromBigint(-1n), +}; + +enum FieldType { + Constant, + Var, + Add, + Scale, +} + +/** + * `FieldVar` is the core data type in snarky. It is eqivalent to `Cvar.t` in OCaml. + * It represents a field element that is part of provable code - either a constant or a variable. + * + * **Variables** end up filling the witness columns of a constraint system. + * Think of a variable as a value that has to be provided by the prover, and that has to satisfy all the + * constraints it is involved in. + * + * **Constants** end up being hard-coded into the constraint system as gate coefficients. + * Think of a constant as a value that is known publicly, at compile time, and that defines the constraint system. + * + * Both constants and variables can be combined into an AST using the Add and Scale combinators. + */ +type FieldVar = + | [FieldType.Constant, FieldConst] + | [FieldType.Var, number] + | [FieldType.Add, FieldVar, FieldVar] + | [FieldType.Scale, FieldConst, FieldVar]; + +type ConstantFieldVar = [FieldType.Constant, FieldConst]; +type VarFieldVar = [FieldType.Var, number]; + +const FieldVar = { + // constructors + Constant(x: FieldConst): ConstantFieldVar { + return [FieldType.Constant, x]; + }, + Var(x: number): VarFieldVar { + return [FieldType.Var, x]; + }, + Add(x: FieldVar, y: FieldVar): [FieldType.Add, FieldVar, FieldVar] { + return [FieldType.Add, x, y]; + }, + Scale(c: FieldConst, x: FieldVar): [FieldType.Scale, FieldConst, FieldVar] { + return [FieldType.Scale, c, x]; + }, + + constant(x: bigint | FieldConst): ConstantFieldVar { + let x0 = typeof x === 'bigint' ? FieldConst.fromBigint(x) : x; + return [FieldType.Constant, x0]; + }, + add(x: FieldVar, y: FieldVar): FieldVar { + if (FieldVar.isConstant(x) && x[1][1] === 0n) return y; + if (FieldVar.isConstant(y) && y[1][1] === 0n) return x; + if (FieldVar.isConstant(x) && FieldVar.isConstant(y)) { + return FieldVar.constant(Fp.add(x[1][1], y[1][1])); + } + return [FieldType.Add, x, y]; + }, + scale(c: bigint | FieldConst, x: FieldVar): FieldVar { + let c0 = typeof c === 'bigint' ? FieldConst.fromBigint(c) : c; + if (c0[1] === 0n) return FieldVar.constant(0n); + if (c0[1] === 1n) return x; + if (FieldVar.isConstant(x)) { + return FieldVar.constant(Fp.mul(c0[1], x[1][1])); + } + if (FieldVar.isScale(x)) { + return [ + FieldType.Scale, + FieldConst.fromBigint(Fp.mul(c0[1], x[1][1])), + x[2], + ]; + } + return [FieldType.Scale, c0, x]; + }, + + // type guards + isConstant(x: FieldVar): x is ConstantFieldVar { + return x[0] === FieldType.Constant; + }, + isVar(x: FieldVar): x is VarFieldVar { + return x[0] === FieldType.Var; + }, + isAdd(x: FieldVar): x is [FieldType.Add, FieldVar, FieldVar] { + return x[0] === FieldType.Add; + }, + isScale(x: FieldVar): x is [FieldType.Scale, FieldConst, FieldVar] { + return x[0] === FieldType.Scale; + }, + + [0]: [FieldType.Constant, FieldConst[0]] satisfies ConstantFieldVar, + [1]: [FieldType.Constant, FieldConst[1]] satisfies ConstantFieldVar, + [-1]: [FieldType.Constant, FieldConst[-1]] satisfies ConstantFieldVar, +}; diff --git a/src/lib/provable/core/provable-context.ts b/src/lib/provable/core/provable-context.ts new file mode 100644 index 0000000000..141d5302db --- /dev/null +++ b/src/lib/provable/core/provable-context.ts @@ -0,0 +1,230 @@ +import { Context } from '../../util/global-context.js'; +import { Gate, GateType, JsonGate, Snarky } from '../../../snarky.js'; +import { parseHexString32 } from '../../../bindings/crypto/bigint-helpers.js'; +import { prettifyStacktrace } from '../../util/errors.js'; +import { Fp } from '../../../bindings/crypto/finite-field.js'; +import { MlBool } from '../../ml/base.js'; + +// internal API +export { + snarkContext, + SnarkContext, + asProver, + runAndCheckSync, + generateWitness, + constraintSystem, + constraintSystemSync, + inProver, + inAnalyze, + inCheckedComputation, + inCompile, + inCompileMode, + gatesFromJson, + printGates, + MlConstraintSystem, +}; + +// global circuit-related context + +type SnarkContext = { + witnesses?: unknown[]; + proverData?: any; + inProver?: boolean; + inCompile?: boolean; + inCheckedComputation?: boolean; + inAnalyze?: boolean; + inWitnessBlock?: boolean; +}; +let snarkContext = Context.create({ default: {} }); + +class MlConstraintSystem { + // opaque +} + +// helpers to read circuit context + +function inProver() { + return !!snarkContext.get().inProver; +} +function inCheckedComputation() { + let ctx = snarkContext.get(); + return !!ctx.inCompile || !!ctx.inProver || !!ctx.inCheckedComputation; +} +function inCompile() { + return !!snarkContext.get().inCompile; +} +function inAnalyze() { + return !!snarkContext.get().inAnalyze; +} + +function inCompileMode() { + let ctx = snarkContext.get(); + return !!ctx.inCompile || !!ctx.inAnalyze; +} + +// runners for provable code + +function asProver(f: () => void) { + if (inCheckedComputation()) { + Snarky.run.asProver(f); + } else { + f(); + } +} + +async function generateWitness( + f: (() => Promise) | (() => void), + { checkConstraints = true } = {} +) { + let id = snarkContext.enter({ inCheckedComputation: true }); + try { + let finish = Snarky.run.enterGenerateWitness(); + if (!checkConstraints) Snarky.run.setEvalConstraints(MlBool(false)); + await f(); + return finish(); + } catch (error) { + throw prettifyStacktrace(error); + } finally { + if (!checkConstraints) Snarky.run.setEvalConstraints(MlBool(true)); + snarkContext.leave(id); + } +} + +/** + * @deprecated use `generateWitness` instead + */ +function runAndCheckSync(f: () => void) { + let id = snarkContext.enter({ inCheckedComputation: true }); + try { + let finish = Snarky.run.enterGenerateWitness(); + f(); + return finish(); + } catch (error) { + throw prettifyStacktrace(error); + } finally { + snarkContext.leave(id); + } +} + +async function constraintSystem(f: (() => Promise) | (() => void)) { + let id = snarkContext.enter({ inAnalyze: true, inCheckedComputation: true }); + try { + let finish = Snarky.run.enterConstraintSystem(); + await f(); + let cs = finish(); + return constraintSystemToJS(cs); + } catch (error) { + throw prettifyStacktrace(error); + } finally { + snarkContext.leave(id); + } +} + +/** + * helper to bridge transition to async circuits + * @deprecated we must get rid of this + */ +function constraintSystemSync(f: () => void) { + let id = snarkContext.enter({ inAnalyze: true, inCheckedComputation: true }); + try { + let finish = Snarky.run.enterConstraintSystem(); + f(); + let cs = finish(); + return constraintSystemToJS(cs); + } catch (error) { + throw prettifyStacktrace(error); + } finally { + snarkContext.leave(id); + } +} + +function constraintSystemToJS(cs: MlConstraintSystem) { + // toJson also "finalizes" the constraint system, which means + // locking in a potential pending single generic gate + let json = Snarky.constraintSystem.toJson(cs); + let rows = Snarky.constraintSystem.rows(cs); + let digest = Snarky.constraintSystem.digest(cs); + let { gates, publicInputSize } = gatesFromJson(json); + return { + rows, + digest, + gates, + publicInputSize, + print() { + printGates(gates); + }, + summary() { + let gateTypes: Partial> = {}; + gateTypes['Total rows'] = rows; + for (let gate of gates) { + gateTypes[gate.type] ??= 0; + gateTypes[gate.type]!++; + } + return gateTypes; + }, + }; +} + +// helpers + +function gatesFromJson(cs: { gates: JsonGate[]; public_input_size: number }) { + let gates: Gate[] = cs.gates.map(({ typ, wires, coeffs: hexCoeffs }) => { + let coeffs = hexCoeffs.map((hex) => parseHexString32(hex).toString()); + return { type: typ, wires, coeffs }; + }); + return { publicInputSize: cs.public_input_size, gates }; +} + +// print a constraint system + +function printGates(gates: Gate[]) { + for (let i = 0, n = gates.length; i < n; i++) { + let { type, wires, coeffs } = gates[i]; + console.log( + i.toString().padEnd(4, ' '), + type.padEnd(15, ' '), + coeffsToPretty(type, coeffs).padEnd(30, ' '), + wiresToPretty(wires, i) + ); + } + console.log(); +} + +let minusRange = Fp.modulus - (1n << 64n); + +function coeffsToPretty(type: Gate['type'], coeffs: Gate['coeffs']): string { + if (coeffs.length === 0) return ''; + if (type === 'Generic' && coeffs.length > 5) { + let first = coeffsToPretty(type, coeffs.slice(0, 5)); + let second = coeffsToPretty(type, coeffs.slice(5)); + return `${first} ${second}`; + } + if (type === 'Poseidon' && coeffs.length > 3) { + return `${coeffsToPretty(type, coeffs.slice(0, 3)).slice(0, -1)} ...]`; + } + let str = coeffs + .map((c) => { + let c0 = BigInt(c); + if (c0 > minusRange) c0 -= Fp.modulus; + let cStr = c0.toString(); + if (cStr.length > 4) return `${cStr.slice(0, 4)}..`; + return cStr; + }) + .join(' '); + return `[${str}]`; +} + +function wiresToPretty(wires: Gate['wires'], row: number) { + let strWires: string[] = []; + let n = wires.length; + for (let col = 0; col < n; col++) { + let wire = wires[col]; + if (wire.row === row && wire.col === col) continue; + if (wire.row === row) { + strWires.push(`${col}->${wire.col}`); + } else { + strWires.push(`${col}->(${wire.row},${wire.col})`); + } + } + return strWires.join(', '); +} diff --git a/src/lib/provable/crypto/crypto.ts b/src/lib/provable/crypto/crypto.ts new file mode 100644 index 0000000000..8a16637195 --- /dev/null +++ b/src/lib/provable/crypto/crypto.ts @@ -0,0 +1,31 @@ +import { CurveParams as CurveParams_ } from '../../../bindings/crypto/elliptic-curve-examples.js'; +import { + CurveAffine, + createCurveAffine, +} from '../../../bindings/crypto/elliptic-curve.js'; + +// crypto namespace +const Crypto = { + /** + * Create elliptic curve arithmetic methods. + */ + createCurve(params: Crypto.CurveParams): Crypto.Curve { + return createCurveAffine(params); + }, + /** + * Parameters defining an elliptic curve in short Weierstraß form + * y^2 = x^3 + ax + b + */ + CurveParams: CurveParams_, +}; + +namespace Crypto { + /** + * Parameters defining an elliptic curve in short Weierstraß form + * y^2 = x^3 + ax + b + */ + export type CurveParams = CurveParams_; + + export type Curve = CurveAffine; +} +export { Crypto }; diff --git a/src/lib/encryption.ts b/src/lib/provable/crypto/encryption.ts similarity index 93% rename from src/lib/encryption.ts rename to src/lib/provable/crypto/encryption.ts index 900db46281..8c025886ab 100644 --- a/src/lib/encryption.ts +++ b/src/lib/provable/crypto/encryption.ts @@ -1,6 +1,6 @@ -import { Field, Scalar, Group } from './core.js'; -import { Poseidon } from './hash.js'; -import { Provable } from './provable.js'; +import { Field, Scalar, Group } from '../wrapped.js'; +import { Poseidon } from './poseidon.js'; +import { Provable } from '../provable.js'; import { PrivateKey, PublicKey } from './signature.js'; export { encrypt, decrypt }; diff --git a/src/lib/provable/crypto/foreign-curve.ts b/src/lib/provable/crypto/foreign-curve.ts new file mode 100644 index 0000000000..a47976bba1 --- /dev/null +++ b/src/lib/provable/crypto/foreign-curve.ts @@ -0,0 +1,312 @@ +import { + CurveParams, + CurveAffine, + createCurveAffine, +} from '../../../bindings/crypto/elliptic-curve.js'; +import type { Group } from '../group.js'; +import { ProvablePureExtended } from '../types/struct.js'; +import { AlmostForeignField, createForeignField } from '../foreign-field.js'; +import { EllipticCurve, Point } from '../gadgets/elliptic-curve.js'; +import { Field3 } from '../gadgets/foreign-field.js'; +import { assert } from '../gadgets/common.js'; +import { Provable } from '../provable.js'; +import { provableFromClass } from '../types/provable-derivers.js'; + +// external API +export { createForeignCurve, ForeignCurve }; + +// internal API +export { toPoint, FlexiblePoint }; + +type FlexiblePoint = { + x: AlmostForeignField | Field3 | bigint | number; + y: AlmostForeignField | Field3 | bigint | number; +}; +function toPoint({ x, y }: ForeignCurve): Point { + return { x: x.value, y: y.value }; +} + +class ForeignCurve { + x: AlmostForeignField; + y: AlmostForeignField; + + /** + * Create a new {@link ForeignCurve} from an object representing the (affine) x and y coordinates. + * + * @example + * ```ts + * let x = new ForeignCurve({ x: 1n, y: 1n }); + * ``` + * + * **Important**: By design, there is no way for a `ForeignCurve` to represent the zero point. + * + * **Warning**: This fails for a constant input which does not represent an actual point on the curve. + */ + constructor(g: { + x: AlmostForeignField | Field3 | bigint | number; + y: AlmostForeignField | Field3 | bigint | number; + }) { + this.x = new this.Constructor.Field(g.x); + this.y = new this.Constructor.Field(g.y); + // don't allow constants that aren't on the curve + if (this.isConstant()) { + this.assertOnCurve(); + this.assertInSubgroup(); + } + } + + /** + * Coerce the input to a {@link ForeignCurve}. + */ + static from(g: ForeignCurve | FlexiblePoint) { + if (g instanceof this) return g; + return new this(g); + } + + /** + * The constant generator point. + */ + static get generator() { + return new this(this.Bigint.one); + } + /** + * The size of the curve's base field. + */ + static get modulus() { + return this.Bigint.modulus; + } + /** + * The size of the curve's base field. + */ + get modulus() { + return this.Constructor.Bigint.modulus; + } + + /** + * Checks whether this curve point is constant. + * + * See {@link FieldVar} to understand constants vs variables. + */ + isConstant() { + return Provable.isConstant(this.Constructor.provable, this); + } + + /** + * Convert this curve point to a point with bigint coordinates. + */ + toBigint() { + return this.Constructor.Bigint.fromNonzero({ + x: this.x.toBigInt(), + y: this.y.toBigInt(), + }); + } + + /** + * Elliptic curve addition. + * + * ```ts + * let r = p.add(q); // r = p + q + * ``` + * + * **Important**: this is _incomplete addition_ and does not handle the degenerate cases: + * - Inputs are equal, `g = h` (where you would use {@link double}). + * In this case, the result of this method is garbage and can be manipulated arbitrarily by a malicious prover. + * - Inputs are inverses of each other, `g = -h`, so that the result would be the zero point. + * In this case, the proof fails. + * + * If you want guaranteed soundness regardless of the input, use {@link addSafe} instead. + * + * @throws if the inputs are inverses of each other. + */ + add(h: ForeignCurve | FlexiblePoint) { + let Curve = this.Constructor.Bigint; + let h_ = this.Constructor.from(h); + let p = EllipticCurve.add(toPoint(this), toPoint(h_), Curve); + return new this.Constructor(p); + } + + /** + * Safe elliptic curve addition. + * + * This is the same as {@link add}, but additionally proves that the inputs are not equal. + * Therefore, the method is guaranteed to either fail or return a valid addition result. + * + * **Beware**: this is more expensive than {@link add}, and is still incomplete in that + * it does not succeed on equal or inverse inputs. + * + * @throws if the inputs are equal or inverses of each other. + */ + addSafe(h: ForeignCurve | FlexiblePoint) { + let h_ = this.Constructor.from(h); + + // prove that we have x1 != x2 => g != +-h + let x1 = this.x.assertCanonical(); + let x2 = h_.x.assertCanonical(); + x1.equals(x2).assertFalse(); + + return this.add(h_); + } + + /** + * Elliptic curve doubling. + * + * @example + * ```ts + * let r = p.double(); // r = 2 * p + * ``` + */ + double() { + let Curve = this.Constructor.Bigint; + let p = EllipticCurve.double(toPoint(this), Curve); + return new this.Constructor(p); + } + + /** + * Elliptic curve negation. + * + * @example + * ```ts + * let r = p.negate(); // r = -p + * ``` + */ + negate(): ForeignCurve { + return new this.Constructor({ x: this.x, y: this.y.neg() }); + } + + /** + * Elliptic curve scalar multiplication, where the scalar is represented as a {@link ForeignField} element. + * + * **Important**: this proves that the result of the scalar multiplication is not the zero point. + * + * @throws if the scalar multiplication results in the zero point; for example, if the scalar is zero. + * + * @example + * ```ts + * let r = p.scale(s); // r = s * p + * ``` + */ + scale(scalar: AlmostForeignField | bigint | number) { + let Curve = this.Constructor.Bigint; + let scalar_ = this.Constructor.Scalar.from(scalar); + let p = EllipticCurve.scale(scalar_.value, toPoint(this), Curve); + return new this.Constructor(p); + } + + static assertOnCurve(g: ForeignCurve) { + EllipticCurve.assertOnCurve(toPoint(g), this.Bigint); + } + + /** + * Assert that this point lies on the elliptic curve, which means it satisfies the equation + * `y^2 = x^3 + ax + b` + */ + assertOnCurve() { + this.Constructor.assertOnCurve(this); + } + + static assertInSubgroup(g: ForeignCurve) { + if (this.Bigint.hasCofactor) { + EllipticCurve.assertInSubgroup(toPoint(g), this.Bigint); + } + } + + /** + * Assert that this point lies in the subgroup defined by `order*P = 0`. + * + * Note: this is a no-op if the curve has cofactor equal to 1. Otherwise + * it performs the full scalar multiplication `order*P` and is expensive. + */ + assertInSubgroup() { + this.Constructor.assertInSubgroup(this); + } + + /** + * Check that this is a valid element of the target subgroup of the curve: + * - Check that the coordinates are valid field elements + * - Use {@link assertOnCurve()} to check that the point lies on the curve + * - If the curve has cofactor unequal to 1, use {@link assertInSubgroup()}. + */ + static check(g: ForeignCurve) { + // more efficient than the automatic check, which would do this for each field separately + this.Field.assertAlmostReduced(g.x, g.y); + this.assertOnCurve(g); + this.assertInSubgroup(g); + } + + // dynamic subclassing infra + get Constructor() { + return this.constructor as typeof ForeignCurve; + } + static _Bigint?: CurveAffine; + static _Field?: typeof AlmostForeignField; + static _Scalar?: typeof AlmostForeignField; + static _provable?: ProvablePureExtended< + ForeignCurve, + { x: string; y: string } + >; + + /** + * Curve arithmetic on JS bigints. + */ + static get Bigint() { + assert(this._Bigint !== undefined, 'ForeignCurve not initialized'); + return this._Bigint; + } + /** + * The base field of this curve as a {@link ForeignField}. + */ + static get Field() { + assert(this._Field !== undefined, 'ForeignCurve not initialized'); + return this._Field; + } + /** + * The scalar field of this curve as a {@link ForeignField}. + */ + static get Scalar() { + assert(this._Scalar !== undefined, 'ForeignCurve not initialized'); + return this._Scalar; + } + /** + * `Provable` + */ + static get provable() { + assert(this._provable !== undefined, 'ForeignCurve not initialized'); + return this._provable; + } +} + +/** + * Create a class representing an elliptic curve group, which is different from the native {@link Group}. + * + * ```ts + * const Curve = createForeignCurve(Crypto.CurveParams.Secp256k1); + * ``` + * + * `createForeignCurve(params)` takes curve parameters {@link CurveParams} as input. + * We support `modulus` and `order` to be prime numbers up to 259 bits. + * + * The returned {@link ForeignCurve} class represents a _non-zero curve point_ and supports standard + * elliptic curve operations like point addition and scalar multiplication. + * + * {@link ForeignCurve} also includes to associated foreign fields: `ForeignCurve.Field` and `ForeignCurve.Scalar`, see {@link createForeignField}. + */ +function createForeignCurve(params: CurveParams): typeof ForeignCurve { + const FieldUnreduced = createForeignField(params.modulus); + const ScalarUnreduced = createForeignField(params.order); + class Field extends FieldUnreduced.AlmostReduced {} + class Scalar extends ScalarUnreduced.AlmostReduced {} + + const BigintCurve = createCurveAffine(params); + + class Curve extends ForeignCurve { + static _Bigint = BigintCurve; + static _Field = Field; + static _Scalar = Scalar; + static _provable = provableFromClass(Curve, { + x: Field.provable, + y: Field.provable, + }); + } + + return Curve; +} diff --git a/src/lib/provable/crypto/foreign-ecdsa.ts b/src/lib/provable/crypto/foreign-ecdsa.ts new file mode 100644 index 0000000000..b601928b93 --- /dev/null +++ b/src/lib/provable/crypto/foreign-ecdsa.ts @@ -0,0 +1,258 @@ +import { provableFromClass } from '../types/provable-derivers.js'; +import { CurveParams } from '../../../bindings/crypto/elliptic-curve.js'; +import { ProvablePureExtended } from '../types/struct.js'; +import { + FlexiblePoint, + ForeignCurve, + createForeignCurve, + toPoint, +} from './foreign-curve.js'; +import { AlmostForeignField } from '../foreign-field.js'; +import { assert } from '../gadgets/common.js'; +import { Field3 } from '../gadgets/foreign-field.js'; +import { Ecdsa } from '../gadgets/elliptic-curve.js'; +import { l } from '../gadgets/range-check.js'; +import { Keccak } from './keccak.js'; +import { Bytes } from '../wrapped-classes.js'; +import { UInt8 } from '../int.js'; + +// external API +export { createEcdsa, EcdsaSignature }; + +type FlexibleSignature = + | EcdsaSignature + | { + r: AlmostForeignField | Field3 | bigint | number; + s: AlmostForeignField | Field3 | bigint | number; + }; + +class EcdsaSignature { + r: AlmostForeignField; + s: AlmostForeignField; + + /** + * Create a new {@link EcdsaSignature} from an object containing the scalars r and s. + * @param signature + */ + constructor(signature: { + r: AlmostForeignField | Field3 | bigint | number; + s: AlmostForeignField | Field3 | bigint | number; + }) { + this.r = new this.Constructor.Curve.Scalar(signature.r); + this.s = new this.Constructor.Curve.Scalar(signature.s); + } + + /** + * Coerce the input to a {@link EcdsaSignature}. + */ + static from(signature: FlexibleSignature): EcdsaSignature { + if (signature instanceof this) return signature; + return new this(signature); + } + + /** + * Create an {@link EcdsaSignature} from a raw 130-char hex string as used in + * [Ethereum transactions](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope). + */ + static fromHex(rawSignature: string): EcdsaSignature { + let s = Ecdsa.Signature.fromHex(rawSignature); + return new this(s); + } + + /** + * Convert this signature to an object with bigint fields. + */ + toBigInt() { + return { r: this.r.toBigInt(), s: this.s.toBigInt() }; + } + + /** + * Verify the ECDSA signature given the message (an array of bytes) and public key (a {@link Curve} point). + * + * **Important:** This method returns a {@link Bool} which indicates whether the signature is valid. + * So, to actually prove validity of a signature, you need to assert that the result is true. + * + * @throws if one of the signature scalars is zero or if the public key is not on the curve. + * + * @example + * ```ts + * // create classes for your curve + * class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} + * class Scalar extends Secp256k1.Scalar {} + * class Ecdsa extends createEcdsa(Secp256k1) {} + * + * let message = 'my message'; + * let messageBytes = new TextEncoder().encode(message); + * + * // outside provable code: create inputs + * let privateKey = Scalar.random(); + * let publicKey = Secp256k1.generator.scale(privateKey); + * let signature = Ecdsa.sign(messageBytes, privateKey.toBigInt()); + * + * // ... + * // in provable code: create input witnesses (or use method inputs, or constants) + * let pk = Provable.witness(Secp256k1.provable, () => publicKey); + * let msg = Provable.witness(Provable.Array(Field, 9), () => messageBytes.map(Field)); + * let sig = Provable.witness(Ecdsa.provable, () => signature); + * + * // verify signature + * let isValid = sig.verify(msg, pk); + * isValid.assertTrue('signature verifies'); + * ``` + */ + verify(message: Bytes, publicKey: FlexiblePoint) { + let msgHashBytes = Keccak.ethereum(message); + let msgHash = keccakOutputToScalar(msgHashBytes, this.Constructor.Curve); + return this.verifySignedHash(msgHash, publicKey); + } + + /** + * Verify the ECDSA signature given the message hash (a {@link Scalar}) and public key (a {@link Curve} point). + * + * This is a building block of {@link EcdsaSignature.verify}, where the input message is also hashed. + * In contrast, this method just takes the message hash (a curve scalar) as input, giving you flexibility in + * choosing the hashing algorithm. + */ + verifySignedHash( + msgHash: AlmostForeignField | bigint, + publicKey: FlexiblePoint + ) { + let msgHash_ = this.Constructor.Curve.Scalar.from(msgHash); + let publicKey_ = this.Constructor.Curve.from(publicKey); + return Ecdsa.verify( + this.Constructor.Curve.Bigint, + toObject(this), + msgHash_.value, + toPoint(publicKey_) + ); + } + + /** + * Create an {@link EcdsaSignature} by signing a message with a private key. + * + * Note: This method is not provable, and only takes JS bigints as input. + */ + static sign(message: (bigint | number)[] | Uint8Array, privateKey: bigint) { + let msgHashBytes = Keccak.ethereum(message); + let msgHash = keccakOutputToScalar(msgHashBytes, this.Curve); + return this.signHash(msgHash.toBigInt(), privateKey); + } + + /** + * Create an {@link EcdsaSignature} by signing a message hash with a private key. + * + * This is a building block of {@link EcdsaSignature.sign}, where the input message is also hashed. + * In contrast, this method just takes the message hash (a curve scalar) as input, giving you flexibility in + * choosing the hashing algorithm. + * + * Note: This method is not provable, and only takes JS bigints as input. + */ + static signHash(msgHash: bigint, privateKey: bigint) { + let { r, s } = Ecdsa.sign(this.Curve.Bigint, msgHash, privateKey); + return new this({ r, s }); + } + + static check(signature: EcdsaSignature) { + // more efficient than the automatic check, which would do this for each scalar separately + this.Curve.Scalar.assertAlmostReduced(signature.r, signature.s); + } + + // dynamic subclassing infra + get Constructor() { + return this.constructor as typeof EcdsaSignature; + } + static _Curve?: typeof ForeignCurve; + static _provable?: ProvablePureExtended< + EcdsaSignature, + { r: string; s: string } + >; + + /** + * The {@link ForeignCurve} on which the ECDSA signature is defined. + */ + static get Curve() { + assert(this._Curve !== undefined, 'EcdsaSignature not initialized'); + return this._Curve; + } + /** + * `Provable` + */ + static get provable() { + assert(this._provable !== undefined, 'EcdsaSignature not initialized'); + return this._provable; + } +} + +/** + * Create a class {@link EcdsaSignature} for verifying ECDSA signatures on the given curve. + */ +function createEcdsa( + curve: CurveParams | typeof ForeignCurve +): typeof EcdsaSignature { + let Curve0: typeof ForeignCurve = + 'b' in curve ? createForeignCurve(curve) : curve; + class Curve extends Curve0 {} + + class Signature extends EcdsaSignature { + static _Curve = Curve; + static _provable = provableFromClass(Signature, { + r: Curve.Scalar.provable, + s: Curve.Scalar.provable, + }); + } + + return Signature; +} + +function toObject(signature: EcdsaSignature) { + return { r: signature.r.value, s: signature.s.value }; +} + +/** + * Provable method to convert keccak256 hash output to ECDSA scalar = "message hash" + * + * Spec from [Wikipedia](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm): + * + * > Let z be the L_n leftmost bits of e, where L_{n} is the bit length of the group order n. + * > (Note that z can be greater than n but not longer.) + * + * The output z is used as input to a multiplication: + * + * > Calculate u_1 = z s^(-1) mod n ... + * + * That means we don't need to reduce z mod n: The fact that it has bitlength <= n makes it + * almost reduced which is enough for the multiplication to be correct. + * (using a weaker notion of "almost reduced" than what we usually prove, but sufficient for all uses of it: `z < 2^ceil(log(n))`) + * + * In summary, this method just: + * - takes a 32 bytes hash + * - converts them to 3 limbs which collectively have L_n <= 256 bits + */ +function keccakOutputToScalar(hash: Bytes, Curve: typeof ForeignCurve) { + const L_n = Curve.Scalar.sizeInBits; + // keep it simple for now, avoid dealing with dropping bits + // TODO: what does "leftmost bits" mean? big-endian or little-endian? + // @noble/curves uses a right shift, dropping the least significant bits: + // https://github.com/paulmillr/noble-curves/blob/4007ee975bcc6410c2e7b504febc1d5d625ed1a4/src/abstract/weierstrass.ts#L933 + assert(L_n === 256, `Scalar sizes ${L_n} !== 256 not supported`); + assert(hash.length === 32, `hash length ${hash.length} !== 32 not supported`); + + // piece together into limbs + // bytes are big-endian, so the first byte is the most significant + assert(l === 88n); + let x2 = bytesToLimbBE(hash.bytes.slice(0, 10)); + let x1 = bytesToLimbBE(hash.bytes.slice(10, 21)); + let x0 = bytesToLimbBE(hash.bytes.slice(21, 32)); + + return new Curve.Scalar.AlmostReduced([x0, x1, x2]); +} + +function bytesToLimbBE(bytes_: UInt8[]) { + let bytes = bytes_.map((x) => x.value); + let n = bytes.length; + let limb = bytes[0]; + for (let i = 1; i < n; i++) { + limb = limb.mul(1n << 8n).add(bytes[i]); + } + return limb.seal(); +} diff --git a/src/lib/hash-generic.ts b/src/lib/provable/crypto/hash-generic.ts similarity index 79% rename from src/lib/hash-generic.ts rename to src/lib/provable/crypto/hash-generic.ts index c5e240ae0a..7ea9bdd353 100644 --- a/src/lib/hash-generic.ts +++ b/src/lib/provable/crypto/hash-generic.ts @@ -1,5 +1,5 @@ -import { GenericField } from '../bindings/lib/generic.js'; -import { prefixToField } from '../bindings/lib/binable.js'; +import { GenericSignableField } from '../../../bindings/lib/generic.js'; +import { prefixToField } from '../../../bindings/lib/binable.js'; export { createHashHelpers, HashHelpers }; @@ -11,7 +11,7 @@ type Hash = { type HashHelpers = ReturnType>; function createHashHelpers( - Field: GenericField, + Field: GenericSignableField, Hash: Hash ) { function salt(prefix: string) { diff --git a/src/lib/provable/crypto/hash.ts b/src/lib/provable/crypto/hash.ts new file mode 100644 index 0000000000..3c7edaf813 --- /dev/null +++ b/src/lib/provable/crypto/hash.ts @@ -0,0 +1,141 @@ +import { Gadgets } from '../gadgets/gadgets.js'; +import { Poseidon } from './poseidon.js'; +import { Keccak } from './keccak.js'; +import { Bytes } from '../wrapped-classes.js'; + +export { Hash }; + +/** + * A collection of hash functions which can be used in provable code. + */ +const Hash = { + /** + * Hashes the given field elements using [Poseidon](https://eprint.iacr.org/2019/458.pdf). Alias for `Poseidon.hash()`. + * + * ```ts + * let hash = Hash.hash([a, b, c]); + * ``` + * + * **Important:** This is by far the most efficient hash function o1js has available in provable code. + * Use it by default, if no compatibility concerns require you to use a different hash function. + * + * The Poseidon implementation operates over the native [Pallas base field](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/) + * and uses parameters generated specifically for the [Mina](https://minaprotocol.com) blockchain. + * + * We use a `rate` of 2, which means that 2 field elements are hashed per permutation. + * A permutation causes 11 rows in the constraint system. + * + * You can find the full set of Poseidon parameters [here](https://github.com/o1-labs/o1js-bindings/blob/main/crypto/constants.ts). + */ + hash: Poseidon.hash, + + /** + * The [Poseidon](https://eprint.iacr.org/2019/458.pdf) hash function. + * + * See {@link Hash.hash} for details and usage examples. + */ + Poseidon, + + /** + * The SHA2 hash function with an output length of 256 bits. + */ + SHA2_256: { + /** + * Hashes the given bytes using SHA2-256. + * + * This is an alias for `Gadgets.SHA256.hash(bytes)`.\ + * See {@link Gadgets.SHA256.hash} for details and usage examples. + */ + hash: Gadgets.SHA256.hash, + }, + + /** + * The SHA3 hash function with an output length of 256 bits. + */ + SHA3_256: { + /** + * Hashes the given bytes using SHA3-256. + * + * This is an alias for `Keccak.nistSha3(256, bytes)`.\ + * See {@link Keccak.nistSha3} for details and usage examples. + */ + hash(bytes: Bytes) { + return Keccak.nistSha3(256, bytes); + }, + }, + + /** + * The SHA3 hash function with an output length of 384 bits. + */ + SHA3_384: { + /** + * Hashes the given bytes using SHA3-384. + * + * This is an alias for `Keccak.nistSha3(384, bytes)`.\ + * See {@link Keccak.nistSha3} for details and usage examples. + */ + hash(bytes: Bytes) { + return Keccak.nistSha3(384, bytes); + }, + }, + + /** + * The SHA3 hash function with an output length of 512 bits. + */ + SHA3_512: { + /** + * Hashes the given bytes using SHA3-512. + * + * This is an alias for `Keccak.nistSha3(512, bytes)`.\ + * See {@link Keccak.nistSha3} for details and usage examples. + */ + hash(bytes: Bytes) { + return Keccak.nistSha3(512, bytes); + }, + }, + + /** + * The pre-NIST Keccak hash function with an output length of 256 bits. + */ + Keccak256: { + /** + * Hashes the given bytes using Keccak-256. + * + * This is an alias for `Keccak.preNist(256, bytes)`.\ + * See {@link Keccak.preNist} for details and usage examples. + */ + hash(bytes: Bytes) { + return Keccak.preNist(256, bytes); + }, + }, + + /** + * The pre-NIST Keccak hash function with an output length of 384 bits. + */ + Keccak384: { + /** + * Hashes the given bytes using Keccak-384. + * + * This is an alias for `Keccak.preNist(384, bytes)`.\ + * See {@link Keccak.preNist} for details and usage examples. + */ + hash(bytes: Bytes) { + return Keccak.preNist(384, bytes); + }, + }, + + /** + * The pre-NIST Keccak hash function with an output length of 512 bits. + */ + Keccak512: { + /** + * Hashes the given bytes using Keccak-512. + * + * This is an alias for `Keccak.preNist(512, bytes)`.\ + * See {@link Keccak.preNist} for details and usage examples. + */ + hash(bytes: Bytes) { + return Keccak.preNist(512, bytes); + }, + }, +}; diff --git a/src/lib/provable/crypto/keccak.ts b/src/lib/provable/crypto/keccak.ts new file mode 100644 index 0000000000..23c95f8a2f --- /dev/null +++ b/src/lib/provable/crypto/keccak.ts @@ -0,0 +1,516 @@ +import { Field } from '../field.js'; +import { Gadgets } from '../gadgets/gadgets.js'; +import { assert } from '../../util/errors.js'; +import { FlexibleBytes } from '../bytes.js'; +import { UInt8 } from '../int.js'; +import { Bytes } from '../wrapped-classes.js'; +import { bytesToWords, wordsToBytes } from '../gadgets/bit-slices.js'; + +export { Keccak }; + +const Keccak = { + /** + * Implementation of [NIST SHA-3](https://csrc.nist.gov/pubs/fips/202/final) Hash Function. + * Supports output lengths of 256, 384, or 512 bits. + * + * Applies the SHA-3 hash function to a list of big-endian byte-sized {@link Field} elements, flexible to handle varying output lengths (256, 384, 512 bits) as specified. + * + * The function accepts {@link Bytes} as the input message, which is a type that represents a static-length list of byte-sized field elements (range-checked using {@link Gadgets.rangeCheck8}). + * Alternatively, you can pass plain `number[]` of `Uint8Array` to perform a hash outside provable code. + * + * Produces an output of {@link Bytes} that conforms to the chosen bit length. + * Both input and output bytes are big-endian. + * + * @param len - Desired output length in bits. Valid options: 256, 384, 512. + * @param message - Big-endian {@link Bytes} representing the message to hash. + * + * ```ts + * let preimage = Bytes.fromString("hello world"); + * let digest256 = Keccak.nistSha3(256, preimage); + * let digest384 = Keccak.nistSha3(384, preimage); + * let digest512 = Keccak.nistSha3(512, preimage); + * ``` + * + */ + nistSha3(len: 256 | 384 | 512, message: FlexibleBytes) { + return nistSha3(len, Bytes.from(message)); + }, + /** + * Ethereum-Compatible Keccak-256 Hash Function. + * This is a specialized variant of {@link Keccak.preNist} configured for a 256-bit output length. + * + * Primarily used in Ethereum for hashing transactions, messages, and other types of payloads. + * + * The function accepts {@link Bytes} as the input message, which is a type that represents a static-length list of byte-sized field elements (range-checked using {@link Gadgets.rangeCheck8}). + * Alternatively, you can pass plain `number[]` of `Uint8Array` to perform a hash outside provable code. + * + * Produces an output of {@link Bytes} of length 32. Both input and output bytes are big-endian. + * + * @param message - Big-endian {@link Bytes} representing the message to hash. + * + * ```ts + * let preimage = Bytes.fromString("hello world"); + * let digest = Keccak.ethereum(preimage); + * ``` + */ + ethereum(message: FlexibleBytes) { + return ethereum(Bytes.from(message)); + }, + /** + * Implementation of [pre-NIST Keccak](https://keccak.team/keccak.html) hash function. + * Supports output lengths of 256, 384, or 512 bits. + * + * Keccak won the SHA-3 competition and was slightly altered before being standardized as SHA-3 by NIST in 2015. + * This variant was used in Ethereum before the NIST standardization, by specifying `len` as 256 bits you can obtain the same hash function as used by Ethereum {@link Keccak.ethereum}. + * + * The function applies the pre-NIST Keccak hash function to a list of byte-sized {@link Field} elements and is flexible to handle varying output lengths (256, 384, 512 bits) as specified. + * + * {@link Keccak.preNist} accepts {@link Bytes} as the input message, which is a type that represents a static-length list of byte-sized field elements (range-checked using {@link Gadgets.rangeCheck8}). + * Alternatively, you can pass plain `number[]` of `Uint8Array` to perform a hash outside provable code. + * + * Produces an output of {@link Bytes} that conforms to the chosen bit length. + * Both input and output bytes are big-endian. + * + * @param len - Desired output length in bits. Valid options: 256, 384, 512. + * @param message - Big-endian {@link Bytes} representing the message to hash. + * + * ```ts + * let preimage = Bytes.fromString("hello world"); + * let digest256 = Keccak.preNist(256, preimage); + * let digest384 = Keccak.preNist(384, preimage); + * let digest512= Keccak.preNist(512, preimage); + * ``` + * + */ + preNist(len: 256 | 384 | 512, message: FlexibleBytes) { + return preNist(len, Bytes.from(message)); + }, +}; + +// KECCAK CONSTANTS + +// Length of the square matrix side of Keccak states +const KECCAK_DIM = 5; + +// Value `l` in Keccak, ranges from 0 to 6 and determines the lane width +const KECCAK_ELL = 6; + +// Width of a lane of the state, meaning the length of each word in bits (64) +const KECCAK_WORD = 2 ** KECCAK_ELL; + +// Number of bytes that fit in a word (8) +const BYTES_PER_WORD = KECCAK_WORD / 8; + +// Length of the state in words, 5x5 = 25 +const KECCAK_STATE_LENGTH_WORDS = KECCAK_DIM ** 2; + +// Length of the state in bits, meaning the 5x5 matrix of words in bits (1600) +const KECCAK_STATE_LENGTH = KECCAK_STATE_LENGTH_WORDS * KECCAK_WORD; + +// Length of the state in bytes, meaning the 5x5 matrix of words in bytes (200) +const KECCAK_STATE_LENGTH_BYTES = KECCAK_STATE_LENGTH / 8; + +// Creates the 5x5 table of rotation offset for Keccak modulo 64 +// | i \ j | 0 | 1 | 2 | 3 | 4 | +// | ----- | -- | -- | -- | -- | -- | +// | 0 | 0 | 36 | 3 | 41 | 18 | +// | 1 | 1 | 44 | 10 | 45 | 2 | +// | 2 | 62 | 6 | 43 | 15 | 61 | +// | 3 | 28 | 55 | 25 | 21 | 56 | +// | 4 | 27 | 20 | 39 | 8 | 14 | +const ROT_TABLE = [ + [0, 36, 3, 41, 18], + [1, 44, 10, 45, 2], + [62, 6, 43, 15, 61], + [28, 55, 25, 21, 56], + [27, 20, 39, 8, 14], +]; + +// Round constants for Keccak +// From https://keccak.team/files/Keccak-reference-3.0.pdf +const ROUND_CONSTANTS = [ + 0x0000000000000001n, + 0x0000000000008082n, + 0x800000000000808an, + 0x8000000080008000n, + 0x000000000000808bn, + 0x0000000080000001n, + 0x8000000080008081n, + 0x8000000000008009n, + 0x000000000000008an, + 0x0000000000000088n, + 0x0000000080008009n, + 0x000000008000000an, + 0x000000008000808bn, + 0x800000000000008bn, + 0x8000000000008089n, + 0x8000000000008003n, + 0x8000000000008002n, + 0x8000000000000080n, + 0x000000000000800an, + 0x800000008000000an, + 0x8000000080008081n, + 0x8000000000008080n, + 0x0000000080000001n, + 0x8000000080008008n, +]; + +// KECCAK HASH FUNCTION + +// Computes the number of required extra bytes to pad a message of length bytes +function bytesToPad(rate: number, length: number): number { + return rate - (length % rate); +} + +// Pads a message M as: +// M || pad[x](|M|) +// The padded message will start with the message argument followed by the padding rule (below) to fulfill a length that is a multiple of rate (in bytes). +// If nist is true, then the padding rule is 0x06 ..0*..1. +// If nist is false, then the padding rule is 10*1. +function pad(message: UInt8[], rate: number, nist: boolean): UInt8[] { + // Find out desired length of the padding in bytes + // If message is already rate bits, need to pad full rate again + const extraBytes = bytesToPad(rate, message.length); + + // 0x06 0x00 ... 0x00 0x80 or 0x86 + const first = nist ? 0x06n : 0x01n; + const last = 0x80n; + + // Create the padding vector + const pad = Array(extraBytes).fill(UInt8.from(0)); + pad[0] = UInt8.from(first); + pad[extraBytes - 1] = pad[extraBytes - 1].add(last); + + // Return the padded message + return [...message, ...pad]; +} + +// ROUND TRANSFORMATION + +// First algorithm in the compression step of Keccak for 64-bit words. +// C[i] = A[i,0] xor A[i,1] xor A[i,2] xor A[i,3] xor A[i,4] +// D[i] = C[i-1] xor ROT(C[i+1], 1) +// E[i,j] = A[i,j] xor D[i] +// In the Keccak reference, it corresponds to the `theta` algorithm. +// We use the first index of the state array as the i coordinate and the second index as the j coordinate. +const theta = (state: Field[][]): Field[][] => { + const stateA = state; + + // XOR the elements of each row together + // for all i in {0..4}: C[i] = A[i,0] xor A[i,1] xor A[i,2] xor A[i,3] xor A[i,4] + const stateC = stateA.map((row) => row.reduce(xor)); + + // for all i in {0..4}: D[i] = C[i-1] xor ROT(C[i+1], 1) + const stateD = Array.from({ length: KECCAK_DIM }, (_, i) => + xor( + stateC[(i + KECCAK_DIM - 1) % KECCAK_DIM], + Gadgets.rotate64(stateC[(i + 1) % KECCAK_DIM], 1, 'left') + ) + ); + + // for all i in {0..4} and j in {0..4}: E[i,j] = A[i,j] xor D[i] + const stateE = stateA.map((row, index) => + row.map((elem) => xor(elem, stateD[index])) + ); + + return stateE; +}; + +// Second and third steps in the compression step of Keccak for 64-bit words. +// pi: A[i,j] = ROT(E[i,j], r[i,j]) +// rho: A[i,j] = A'[j, 2i+3j mod KECCAK_DIM] +// piRho: B[j,2i+3j] = ROT(E[i,j], r[i,j]) +// which is equivalent to the `rho` algorithm followed by the `pi` algorithm in the Keccak reference as follows: +// rho: +// A[0,0] = a[0,0] +// | i | = | 1 | +// | j | = | 0 | +// for t = 0 to 23 do +// A[i,j] = ROT(a[i,j], (t+1)(t+2)/2 mod 64))) +// | i | = | 0 1 | | i | +// | | = | | * | | +// | j | = | 2 3 | | j | +// end for +// pi: +// for i = 0 to 4 do +// for j = 0 to 4 do +// | I | = | 0 1 | | i | +// | | = | | * | | +// | J | = | 2 3 | | j | +// A[I,J] = a[i,j] +// end for +// end for +// We use the first index of the state array as the i coordinate and the second index as the j coordinate. +function piRho(state: Field[][]): Field[][] { + const stateE = state; + const stateB = State.zeros(); + + // for all i in {0..4} and j in {0..4}: B[j,2i+3j] = ROT(E[i,j], r[i,j]) + for (let i = 0; i < KECCAK_DIM; i++) { + for (let j = 0; j < KECCAK_DIM; j++) { + stateB[j][(2 * i + 3 * j) % KECCAK_DIM] = Gadgets.rotate64( + stateE[i][j], + ROT_TABLE[i][j], + 'left' + ); + } + } + + return stateB; +} + +// Fourth step of the compression function of Keccak for 64-bit words. +// F[i,j] = B[i,j] xor ((not B[i+1,j]) and B[i+2,j]) +// It corresponds to the chi algorithm in the Keccak reference. +// for j = 0 to 4 do +// for i = 0 to 4 do +// A[i,j] = a[i,j] xor ((not a[i+1,j]) and a[i+2,j]) +// end for +// end for +function chi(state: Field[][]): Field[][] { + const stateB = state; + const stateF = State.zeros(); + + // for all i in {0..4} and j in {0..4}: F[i,j] = B[i,j] xor ((not B[i+1,j]) and B[i+2,j]) + for (let i = 0; i < KECCAK_DIM; i++) { + for (let j = 0; j < KECCAK_DIM; j++) { + stateF[i][j] = xor( + stateB[i][j], + Gadgets.and( + // We can use unchecked NOT because the length of the input is constrained to be 64 bits thanks to the fact that it is the output of a previous Xor64 + Gadgets.not(stateB[(i + 1) % KECCAK_DIM][j], KECCAK_WORD, false), + stateB[(i + 2) % KECCAK_DIM][j], + KECCAK_WORD + ) + ); + } + } + + return stateF; +} + +// Fifth step of the permutation function of Keccak for 64-bit words. +// It takes the word located at the position (0,0) of the state and XORs it with the round constant. +function iota(state: Field[][], rc: bigint): Field[][] { + const stateG = state; + + stateG[0][0] = xor(stateG[0][0], Field.from(rc)); + + return stateG; +} + +// One round of the Keccak permutation function. +// iota o chi o pi o rho o theta +function round(state: Field[][], rc: bigint): Field[][] { + const stateA = state; + const stateE = theta(stateA); + const stateB = piRho(stateE); + const stateF = chi(stateB); + const stateD = iota(stateF, rc); + return stateD; +} + +// Keccak permutation function with a constant number of rounds +function permutation(state: Field[][], rcs: bigint[]): Field[][] { + return rcs.reduce((state, rc) => round(state, rc), state); +} + +// KECCAK SPONGE + +// Absorb padded message into a keccak state with given rate and capacity +function absorb( + paddedMessage: Field[], + capacity: number, + rate: number, + rc: bigint[] +): State { + assert( + rate + capacity === KECCAK_STATE_LENGTH_WORDS, + `invalid rate or capacity (rate + capacity should be ${KECCAK_STATE_LENGTH_WORDS})` + ); + assert( + paddedMessage.length % rate === 0, + 'invalid padded message length (should be multiple of rate)' + ); + + let state = State.zeros(); + + // array of capacity zero words + const zeros = Array(capacity).fill(Field.from(0)); + + for (let idx = 0; idx < paddedMessage.length; idx += rate) { + // split into blocks of rate words + const block = paddedMessage.slice(idx, idx + rate); + // pad the block with 0s to up to KECCAK_STATE_LENGTH_WORDS words + const paddedBlock = block.concat(zeros); + // convert the padded block to a Keccak state + const blockState = State.fromWords(paddedBlock); + // xor the state with the padded block + const stateXor = State.xor(state, blockState); + // apply the permutation function to the xored state + state = permutation(stateXor, rc); + } + return state; +} + +// Squeeze state until it has a desired length in words +function squeeze(state: State, length: number, rate: number): Field[] { + // number of squeezes + const squeezes = Math.floor(length / rate) + 1; + assert(squeezes === 1, 'squeezes should be 1'); + + // Obtain the hash selecting the first `length` words of the output array + const words = State.toWords(state); + const hashed = words.slice(0, length); + return hashed; +} + +// Keccak sponge function for 200 bytes of state width +function sponge( + paddedMessage: Field[], + length: number, + capacity: number, + rate: number +): Field[] { + // check that the padded message is a multiple of rate + assert(paddedMessage.length % rate === 0, 'Invalid padded message length'); + + // absorb + const state = absorb(paddedMessage, capacity, rate, ROUND_CONSTANTS); + + // squeeze + const hashed = squeeze(state, length, rate); + return hashed; +} + +// Keccak hash function with input message passed as list of Field bytes. +// The message will be parsed as follows: +// - the first byte of the message will be the least significant byte of the first word of the state (A[0][0]) +// - the 10*1 pad will take place after the message, until reaching the bit length rate. +// - then, {0} pad will take place to finish the 200 bytes of the state. +function hash( + message: Bytes, + length: number, + capacity: number, + nistVersion: boolean +): UInt8[] { + // Throw errors if used improperly + assert(capacity > 0, 'capacity must be positive'); + assert( + capacity < KECCAK_STATE_LENGTH_BYTES, + `capacity must be less than ${KECCAK_STATE_LENGTH_BYTES}` + ); + assert(length > 0, 'length must be positive'); + + // convert capacity and length to word units + assert(capacity % BYTES_PER_WORD === 0, 'length must be a multiple of 8'); + capacity /= BYTES_PER_WORD; + assert(length % BYTES_PER_WORD === 0, 'length must be a multiple of 8'); + length /= BYTES_PER_WORD; + + const rate = KECCAK_STATE_LENGTH_WORDS - capacity; + + // apply padding, convert to words, and hash + const paddedBytes = pad(message.bytes, rate * BYTES_PER_WORD, nistVersion); + const padded = bytesToWords(paddedBytes); + + const hash = sponge(padded, length, capacity, rate); + const hashBytes = wordsToBytes(hash); + + return hashBytes; +} + +// Gadget for NIST SHA-3 function for output lengths 256/384/512. +function nistSha3(len: 256 | 384 | 512, message: Bytes): Bytes { + let bytes = hash(message, len / 8, len / 4, true); + return BytesOfBitlength[len].from(bytes); +} + +// Gadget for pre-NIST SHA-3 function for output lengths 256/384/512. +// Note that when calling with output length 256 this is equivalent to the ethereum function +function preNist(len: 256 | 384 | 512, message: Bytes): Bytes { + let bytes = hash(message, len / 8, len / 4, false); + return BytesOfBitlength[len].from(bytes); +} + +// Gadget for Keccak hash function for the parameters used in Ethereum. +function ethereum(message: Bytes): Bytes { + return preNist(256, message); +} + +// FUNCTIONS ON KECCAK STATE + +type State = Field[][]; +const State = { + /** + * Create a state of all zeros + */ + zeros(): State { + return Array.from(Array(KECCAK_DIM), (_) => + Array(KECCAK_DIM).fill(Field.from(0)) + ); + }, + + /** + * Flatten state to words + */ + toWords(state: State): Field[] { + const words = Array(KECCAK_STATE_LENGTH_WORDS); + for (let j = 0; j < KECCAK_DIM; j++) { + for (let i = 0; i < KECCAK_DIM; i++) { + words[KECCAK_DIM * j + i] = state[i][j]; + } + } + return words; + }, + + /** + * Compose words to state + */ + fromWords(words: Field[]): State { + const state = State.zeros(); + for (let j = 0; j < KECCAK_DIM; j++) { + for (let i = 0; i < KECCAK_DIM; i++) { + state[i][j] = words[KECCAK_DIM * j + i]; + } + } + return state; + }, + + /** + * XOR two states together and return the result + */ + xor(a: State, b: State): State { + assert( + a.length === KECCAK_DIM && a[0].length === KECCAK_DIM, + `invalid \`a\` dimensions (should be ${KECCAK_DIM})` + ); + assert( + b.length === KECCAK_DIM && b[0].length === KECCAK_DIM, + `invalid \`b\` dimensions (should be ${KECCAK_DIM})` + ); + + // Calls xor() on each pair (i,j) of the states input1 and input2 and outputs the output Fields as a new matrix + return a.map((row, i) => row.map((x, j) => xor(x, b[i][j]))); + }, +}; + +// AUXILIARY TYPES + +class Bytes32 extends Bytes(32) {} +class Bytes48 extends Bytes(48) {} +class Bytes64 extends Bytes(64) {} + +const BytesOfBitlength = { + 256: Bytes32, + 384: Bytes48, + 512: Bytes64, +}; + +// xor which avoids doing anything on 0 inputs +// (but doesn't range-check the other input in that case) +function xor(x: Field, y: Field): Field { + if (x.isConstant() && x.toBigInt() === 0n) return y; + if (y.isConstant() && y.toBigInt() === 0n) return x; + return Gadgets.xor(x, y, 64); +} diff --git a/src/lib/nullifier.ts b/src/lib/provable/crypto/nullifier.ts similarity index 92% rename from src/lib/nullifier.ts rename to src/lib/provable/crypto/nullifier.ts index 0b4c838751..f0ce72a65b 100644 --- a/src/lib/nullifier.ts +++ b/src/lib/provable/crypto/nullifier.ts @@ -1,10 +1,10 @@ -import type { Nullifier as JsonNullifier } from '../mina-signer/src/TSTypes.js'; -import { Struct } from './circuit_value.js'; -import { Field, Group, Scalar } from './core.js'; -import { Poseidon } from './hash.js'; -import { MerkleMapWitness } from './merkle_map.js'; +import type { Nullifier as JsonNullifier } from '../../../mina-signer/src/types.js'; +import { Struct } from '../types/struct.js'; +import { Field, Group, Scalar } from '../wrapped.js'; +import { Poseidon } from './poseidon.js'; +import { MerkleMapWitness } from '../merkle-map.js'; import { PrivateKey, PublicKey, scaleShifted } from './signature.js'; -import { Provable } from './provable.js'; +import { Provable } from '../provable.js'; export { Nullifier }; @@ -13,7 +13,7 @@ export { Nullifier }; * Nullifiers are used as a public commitment to a specific anonymous account, * to forbid actions like double spending, or allow a consistent identity between anonymous actions. * - * RFC: https://github.com/o1-labs/snarkyjs/issues/756 + * RFC: https://github.com/o1-labs/o1js/issues/756 * * Paper: https://eprint.iacr.org/2022/1255.pdf */ @@ -68,7 +68,7 @@ class Nullifier extends Struct({ let h_m_pk = Group.fromFields([x, x0]); - // shifted scalar see https://github.com/o1-labs/snarkyjs/blob/5333817a62890c43ac1b9cb345748984df271b62/src/lib/signature.ts#L220 + // shifted scalar see https://github.com/o1-labs/o1js/blob/5333817a62890c43ac1b9cb345748984df271b62/src/lib/signature.ts#L220 // pk^c let pk_c = scaleShifted(this.publicKey, Scalar.fromBits(c.toBits())); @@ -76,7 +76,7 @@ class Nullifier extends Struct({ let g_r = G.scale(s).sub(pk_c); // h(m, pk)^s - let h_m_pk_s = Group.scale(h_m_pk, s); + let h_m_pk_s = h_m_pk.scale(s); // h_m_pk_r = h(m,pk)^s / nullifier^c let h_m_pk_s_div_nullifier_s = h_m_pk_s.sub( diff --git a/src/lib/hash.ts b/src/lib/provable/crypto/poseidon.ts similarity index 66% rename from src/lib/hash.ts rename to src/lib/provable/crypto/poseidon.ts index 9abf35b662..33be685443 100644 --- a/src/lib/hash.ts +++ b/src/lib/provable/crypto/poseidon.ts @@ -1,41 +1,48 @@ -import { HashInput, ProvableExtended, Struct } from './circuit_value.js'; -import { Snarky } from '../snarky.js'; -import { Field } from './core.js'; +import { HashInput, ProvableExtended, Struct } from '../types/struct.js'; +import { Snarky } from '../../../snarky.js'; +import { Field } from '../wrapped.js'; import { createHashHelpers } from './hash-generic.js'; -import { Provable } from './provable.js'; -import { MlFieldArray } from './ml/fields.js'; -import { Poseidon as PoseidonBigint } from '../bindings/crypto/poseidon.js'; -import { assert } from './errors.js'; +import { Provable } from '../provable.js'; +import { MlFieldArray } from '../../ml/fields.js'; +import { Poseidon as PoseidonBigint } from '../../../bindings/crypto/poseidon.js'; +import { assert } from '../../util/errors.js'; +import { rangeCheckN } from '../gadgets/range-check.js'; +import { TupleN } from '../../util/types.js'; // external API export { Poseidon, TokenSymbol }; // internal API export { + ProvableHashable, HashInput, - Hash, + HashHelpers, emptyHashWithPrefix, hashWithPrefix, salt, packToFields, emptyReceiptChainHash, hashConstant, + isHashable, }; +type Hashable = { toInput: (x: T) => HashInput; empty: () => T }; +type ProvableHashable = Provable & Hashable; + class Sponge { - private sponge: unknown; + #sponge: unknown; constructor() { let isChecked = Provable.inCheckedComputation(); - this.sponge = Snarky.poseidon.sponge.create(isChecked); + this.#sponge = Snarky.poseidon.sponge.create(isChecked); } absorb(x: Field) { - Snarky.poseidon.sponge.absorb(this.sponge, x.value); + Snarky.poseidon.sponge.absorb(this.#sponge, x.value); } squeeze(): Field { - return Field(Snarky.poseidon.sponge.squeeze(this.sponge)); + return Field(Snarky.poseidon.sponge.squeeze(this.#sponge)); } } @@ -44,13 +51,13 @@ const Poseidon = { if (isConstant(input)) { return Field(PoseidonBigint.hash(toBigints(input))); } - return Poseidon.update(this.initialState(), input)[0]; + return Poseidon.update(Poseidon.initialState(), input)[0]; }, update(state: [Field, Field, Field], input: Field[]) { if (isConstant(state) && isConstant(input)) { let newState = PoseidonBigint.update(toBigints(state), toBigints(input)); - return newState.map(Field); + return TupleN.fromArray(3, newState.map(Field)); } let newState = Snarky.poseidon.update( @@ -60,11 +67,22 @@ const Poseidon = { return MlFieldArray.from(newState) as [Field, Field, Field]; }, + hashWithPrefix(prefix: string, input: Field[]) { + let init = Poseidon.update(Poseidon.initialState(), [ + prefixToField(prefix), + ]); + return Poseidon.update(init, input)[0]; + }, + + initialState(): [Field, Field, Field] { + return [Field(0), Field(0), Field(0)]; + }, + hashToGroup(input: Field[]) { if (isConstant(input)) { let result = PoseidonBigint.hashToGroup(toBigints(input)); assert(result !== undefined, 'hashToGroup works on all inputs'); - let { x, y } = result!; + let { x, y } = result; return { x: Field(x), y: { x0: Field(y.x0), x1: Field(y.x1) }, @@ -94,8 +112,22 @@ const Poseidon = { return { x, y: { x0, x1 } }; }, - initialState(): [Field, Field, Field] { - return [Field(0), Field(0), Field(0)]; + /** + * Hashes a provable type efficiently. + * + * ```ts + * let skHash = Poseidon.hashPacked(PrivateKey, secretKey); + * ``` + * + * Note: Instead of just doing `Poseidon.hash(value.toFields())`, this + * uses the `toInput()` method on the provable type to pack the input into as few + * field elements as possible. This saves constraints because packing has a much + * lower per-field element cost than hashing. + */ + hashPacked(type: Hashable, value: T) { + let input = type.toInput(value); + let packed = packToFields(input); + return Poseidon.hash(packed); }, Sponge, @@ -105,8 +137,8 @@ function hashConstant(input: Field[]) { return Field(PoseidonBigint.hash(toBigints(input))); } -const Hash = createHashHelpers(Field, Poseidon); -let { salt, emptyHashWithPrefix, hashWithPrefix } = Hash; +const HashHelpers = createHashHelpers(Field, Poseidon); +let { salt, emptyHashWithPrefix, hashWithPrefix } = HashHelpers; // same as Random_oracle.prefix_to_field in OCaml function prefixToField(prefix: string) { @@ -149,6 +181,15 @@ function packToFields({ fields = [], packed = [] }: HashInput) { return fields.concat(packedBits); } +function isHashable(obj: any): obj is Hashable { + if (!obj) { + return false; + } + const hasToInput = 'toInput' in obj && typeof obj.toInput === 'function'; + const hasEmpty = 'empty' in obj && typeof obj.empty === 'function'; + return hasToInput && hasEmpty; +} + const TokenSymbolPure: ProvableExtended< { symbol: string; field: Field }, string @@ -166,8 +207,7 @@ const TokenSymbolPure: ProvableExtended< return 1; }, check({ field }: TokenSymbol) { - let actual = field.rangeCheckHelper(48); - actual.assertEquals(field); + rangeCheckN(48, field); }, toJSON({ symbol }) { return symbol; @@ -179,12 +219,11 @@ const TokenSymbolPure: ProvableExtended< toInput({ field }) { return { packed: [[field, 48]] }; }, + empty() { + return { symbol: '', field: Field(0n) }; + }, }; class TokenSymbol extends Struct(TokenSymbolPure) { - static get empty() { - return { symbol: '', field: Field(0) }; - } - static from(symbol: string): TokenSymbol { let bytesLength = new TextEncoder().encode(symbol).length; if (bytesLength > 6) diff --git a/src/lib/signature.ts b/src/lib/provable/crypto/signature.ts similarity index 71% rename from src/lib/signature.ts rename to src/lib/provable/crypto/signature.ts index e26e68ca2b..9a6159f933 100644 --- a/src/lib/signature.ts +++ b/src/lib/provable/crypto/signature.ts @@ -1,19 +1,19 @@ -import { Field, Bool, Group, Scalar } from './core.js'; -import { prop, CircuitValue, AnyConstructor } from './circuit_value.js'; -import { hashWithPrefix } from './hash.js'; +import { Field, Bool, Group, Scalar } from '../wrapped.js'; +import { AnyConstructor } from '../types/struct.js'; +import { hashWithPrefix } from './poseidon.js'; +import { Fq } from '../../../bindings/crypto/finite-field.js'; import { deriveNonce, Signature as SignatureBigint, -} from '../mina-signer/src/signature.js'; -import { Bool as BoolBigint } from '../provable/field-bigint.js'; + signaturePrefix, +} from '../../../mina-signer/src/signature.js'; import { - Scalar as ScalarBigint, PrivateKey as PrivateKeyBigint, PublicKey as PublicKeyBigint, -} from '../provable/curve-bigint.js'; -import { prefixes } from '../bindings/crypto/constants.js'; -import { constantScalarToBigint } from './scalar.js'; -import { toConstantField } from './field.js'; +} from '../../../mina-signer/src/curve-bigint.js'; +import { constantScalarToBigint } from '../scalar.js'; +import { toConstantField } from '../field.js'; +import { CircuitValue, prop } from '../types/circuit-value.js'; // external API export { PrivateKey, PublicKey, Signature }; @@ -32,9 +32,13 @@ class PrivateKey extends CircuitValue { } /** - * You can use this method to generate a private key. You can then obtain - * the associated public key via {@link toPublicKey}. And generate signatures - * via {@link Signature.create}. + * Generate a random private key. + * + * You can obtain the associated public key via {@link toPublicKey}. + * And generate signatures via {@link Signature.create}. + * + * Note: This uses node or browser built-in APIs to obtain cryptographically strong randomness, + * and can be safely used to generate a real private key. * * @returns a new {@link PrivateKey}. */ @@ -42,6 +46,17 @@ class PrivateKey extends CircuitValue { return new PrivateKey(Scalar.random()); } + /** + * Create a random keypair `{ privateKey: PrivateKey, publicKey: PublicKey }`. + * + * Note: This uses node or browser built-in APIs to obtain cryptographically strong randomness, + * and can be safely used to generate a real keypair. + */ + static randomKeypair() { + let privateKey = PrivateKey.random(); + return { privateKey, publicKey: privateKey.toPublicKey() }; + } + /** * Deserializes a list of bits into a {@link PrivateKey}. * @@ -65,7 +80,7 @@ class PrivateKey extends CircuitValue { * **Warning**: Private keys should be sampled from secure randomness with sufficient entropy. * Be careful that you don't use this method to create private keys that were sampled insecurely. */ - static fromBigInt(sk: PrivateKeyBigint) { + static fromBigInt(sk: bigint) { return new PrivateKey(Scalar.from(sk)); } @@ -124,15 +139,14 @@ class PublicKey extends CircuitValue { */ toGroup(): Group { // compute y from elliptic curve equation y^2 = x^3 + 5 - // TODO: we have to improve constraint efficiency by using range checks let { x, isOdd } = this; - let ySquared = x.mul(x).mul(x).add(5); - let someY = ySquared.sqrt(); - let isTheRightY = isOdd.equals(someY.toBits()[0]); - let y = isTheRightY - .toField() - .mul(someY) - .add(isTheRightY.not().toField().mul(someY.neg())); + let y = x.square().mul(x).add(5).sqrt(); + + // negate y if its parity is different from the public key's + let sameParity = y.isOdd().equals(isOdd).toField(); + let sign = sameParity.mul(2).sub(1); // (2*sameParity - 1) == 1 if same parity, -1 if different parity + y = y.mul(sign); + return new Group({ x, y }); } @@ -141,8 +155,7 @@ class PublicKey extends CircuitValue { * @returns a {@link PublicKey}. */ static fromGroup({ x, y }: Group): PublicKey { - let isOdd = y.toBits()[0]; - return PublicKey.fromObject({ x, isOdd }); + return PublicKey.fromObject({ x, isOdd: y.isOdd() }); } /** @@ -165,8 +178,8 @@ class PublicKey extends CircuitValue { * Creates an empty {@link PublicKey}. * @returns an empty {@link PublicKey} */ - static empty() { - return PublicKey.from({ x: Field(0), isOdd: Bool(false) }); + static empty(): InstanceType { + return PublicKey.from({ x: Field(0), isOdd: Bool(false) }) as any; } /** @@ -175,7 +188,7 @@ class PublicKey extends CircuitValue { */ isEmpty() { // there are no curve points with x === 0 - return this.x.isZero(); + return this.x.equals(0); } /** @@ -203,7 +216,7 @@ class PublicKey extends CircuitValue { x = toConstantField(x, 'toBase58', 'pk', 'public key'); return PublicKeyBigint.toBase58({ x: x.toBigInt(), - isOdd: BoolBigint(isOdd.toBoolean()), + isOdd: isOdd.toBoolean() ? 1n : 0n, }); } @@ -238,18 +251,21 @@ class Signature extends CircuitValue { static create(privKey: PrivateKey, msg: Field[]): Signature { const publicKey = PublicKey.fromPrivateKey(privKey).toGroup(); const d = privKey.s; - const kPrime = Scalar.fromBigInt( + // we chose an arbitrary prefix for the signature, and it happened to be 'testnet' + // there's no consequences in practice and the signatures can be used with any network + // if there needs to be a custom nonce, include it in the message itself + const kPrime = Scalar.from( deriveNonce( { fields: msg.map((f) => f.toBigInt()) }, { x: publicKey.x.toBigInt(), y: publicKey.y.toBigInt() }, - BigInt(d.toJSON()), + d.toBigInt(), 'testnet' ) ); let { x: r, y: ry } = Group.generator.scale(kPrime); - const k = ry.toBits()[0].toBoolean() ? kPrime.neg() : kPrime; + const k = ry.isOdd().toBoolean() ? kPrime.neg() : kPrime; let h = hashWithPrefix( - prefixes.signatureTestnet, + signaturePrefix('testnet'), msg.concat([publicKey.x, publicKey.y, r]) ); // TODO: Scalar.fromBits interprets the input as a "shifted scalar" @@ -265,15 +281,18 @@ class Signature extends CircuitValue { */ verify(publicKey: PublicKey, msg: Field[]): Bool { const point = publicKey.toGroup(); + // we chose an arbitrary prefix for the signature, and it happened to be 'testnet' + // there's no consequences in practice and the signatures can be used with any network + // if there needs to be a custom nonce, include it in the message itself let h = hashWithPrefix( - prefixes.signatureTestnet, + signaturePrefix('testnet'), msg.concat([point.x, point.y, this.r]) ); // TODO: Scalar.fromBits interprets the input as a "shifted scalar" // therefore we have to use scaleShifted which is very inefficient let e = Scalar.fromBits(h.toBits()); let r = scaleShifted(point, e).neg().add(Group.generator.scale(this.s)); - return Bool.and(r.x.equals(this.r), r.y.toBits()[0].equals(false)); + return r.x.equals(this.r).and(r.y.isEven()); } /** @@ -281,17 +300,14 @@ class Signature extends CircuitValue { */ static fromBase58(signatureBase58: string) { let { r, s } = SignatureBigint.fromBase58(signatureBase58); - return Signature.fromObject({ - r: Field(r), - s: Scalar.fromJSON(s.toString()), - }); + return Signature.fromObject({ r: Field(r), s: Scalar.from(s) }); } /** * Encodes a {@link Signature} in base58 format. */ toBase58() { let r = this.r.toBigInt(); - let s = BigInt(this.s.toJSON()); + let s = this.s.toBigInt(); return SignatureBigint.toBase58({ r, s }); } } @@ -299,17 +315,15 @@ class Signature extends CircuitValue { // performs scalar multiplication s*G assuming that instead of s, we got s' = 2s + 1 + 2^255 // cost: 2x scale by constant, 1x scale by variable function scaleShifted(point: Group, shiftedScalar: Scalar) { - let oneHalfGroup = point.scale(Scalar.fromBigInt(oneHalf)); - let shiftGroup = oneHalfGroup.scale(Scalar.fromBigInt(shift)); + let oneHalfGroup = point.scale(Scalar.from(oneHalf)); + let shiftGroup = oneHalfGroup.scale(Scalar.from(shift)); return oneHalfGroup.scale(shiftedScalar).sub(shiftGroup); } // returns s, assuming that instead of s, we got s' = 2s + 1 + 2^255 // (only works out of snark) function unshift(shiftedScalar: Scalar) { - return shiftedScalar - .sub(Scalar.fromBigInt(shift)) - .mul(Scalar.fromBigInt(oneHalf)); + return shiftedScalar.sub(Scalar.from(shift)).mul(Scalar.from(oneHalf)); } -let shift = ScalarBigint(1n + 2n ** 255n); -let oneHalf = ScalarBigint.inverse(2n)!; +let shift = Fq.mod(1n + 2n ** 255n); +let oneHalf = Fq.inverse(2n)!; diff --git a/src/lib/field.ts b/src/lib/provable/field.ts similarity index 67% rename from src/lib/field.ts rename to src/lib/provable/field.ts index f078922e17..45ad9059db 100644 --- a/src/lib/field.ts +++ b/src/lib/provable/field.ts @@ -1,10 +1,34 @@ -import { Snarky, Provable } from '../snarky.js'; -import { Field as Fp } from '../provable/field-bigint.js'; -import { defineBinable } from '../bindings/lib/binable.js'; -import type { NonNegativeInteger } from '../bindings/crypto/non-negative.js'; -import { asProver, inCheckedComputation } from './provable-context.js'; +import { Snarky } from '../../snarky.js'; +import { Fp } from '../../bindings/crypto/finite-field.js'; +import { BinableFp, SignableFp } from '../../mina-signer/src/field-bigint.js'; +import { defineBinable } from '../../bindings/lib/binable.js'; +import type { NonNegativeInteger } from '../../bindings/crypto/non-negative.js'; +import { inCheckedComputation } from './core/provable-context.js'; import { Bool } from './bool.js'; -import { assert } from './errors.js'; +import { assert } from '../util/errors.js'; +import { Provable } from './provable.js'; +import { + assertEqual, + assertMul, + assertSquare, + assertBoolean, +} from './gadgets/compatible.js'; +import { toLinearCombination } from './gadgets/basic.js'; +import { + FieldType, + FieldVar, + FieldConst, + VarFieldVar, + ConstantFieldVar, +} from './core/fieldvar.js'; +import { exists, existsOne } from './core/exists.js'; +import { setFieldConstructor } from './core/field-constructor.js'; +import { + assertLessThanFull, + assertLessThanOrEqualFull, + lessThanFull, + lessThanOrEqualFull, +} from './gadgets/comparison.js'; // external API export { Field }; @@ -12,88 +36,16 @@ export { Field }; // internal API export { ConstantField, - FieldType, - FieldVar, - FieldConst, - isField, + VarField, withMessage, readVarMessage, toConstantField, -}; - -type FieldConst = Uint8Array; - -function constToBigint(x: FieldConst): Fp { - return Fp.fromBytes([...x]); -} -function constFromBigint(x: Fp) { - return Uint8Array.from(Fp.toBytes(x)); -} - -const FieldConst = { - fromBigint: constFromBigint, - toBigint: constToBigint, - equal(x: FieldConst, y: FieldConst) { - for (let i = 0, n = Fp.sizeInBytes(); i < n; i++) { - if (x[i] !== y[i]) return false; - } - return true; - }, - [0]: constFromBigint(0n), - [1]: constFromBigint(1n), - [-1]: constFromBigint(Fp(-1n)), -}; - -enum FieldType { - Constant, - Var, - Add, - Scale, -} - -/** - * `FieldVar` is the core data type in snarky. It is eqivalent to `Cvar.t` in OCaml. - * It represents a field element that is part of provable code - either a constant or a variable. - * - * **Variables** end up filling the witness columns of a constraint system. - * Think of a variable as a value that has to be provided by the prover, and that has to satisfy all the - * constraints it is involved in. - * - * **Constants** end up being hard-coded into the constraint system as gate coefficients. - * Think of a constant as a value that is known publicly, at compile time, and that defines the constraint system. - * - * Both constants and variables can be combined into an AST using the Add and Scale combinators. - */ -type FieldVar = - | [FieldType.Constant, FieldConst] - | [FieldType.Var, number] - | [FieldType.Add, FieldVar, FieldVar] - | [FieldType.Scale, FieldConst, FieldVar]; - -type ConstantFieldVar = [FieldType.Constant, FieldConst]; - -const FieldVar = { - constant(x: bigint | FieldConst): ConstantFieldVar { - let x0 = typeof x === 'bigint' ? FieldConst.fromBigint(x) : x; - return [FieldType.Constant, x0]; - }, - isConstant(x: FieldVar): x is ConstantFieldVar { - return x[0] === FieldType.Constant; - }, - // TODO: handle (special) constants - add(x: FieldVar, y: FieldVar): FieldVar { - return [FieldType.Add, x, y]; - }, - // TODO: handle (special) constants - scale(c: FieldConst, x: FieldVar): FieldVar { - return [FieldType.Scale, c, x]; - }, - [0]: [FieldType.Constant, FieldConst[0]] satisfies ConstantFieldVar, - [1]: [FieldType.Constant, FieldConst[1]] satisfies ConstantFieldVar, - [-1]: [FieldType.Constant, FieldConst[-1]] satisfies ConstantFieldVar, + toFp, + checkBitLength, }; type ConstantField = Field & { value: ConstantFieldVar }; +type VarField = Field & { value: VarFieldVar }; /** * A {@link Field} is an element of a prime order [finite field](https://en.wikipedia.org/wiki/Finite_field). @@ -104,7 +56,7 @@ type ConstantField = Field & { value: ConstantFieldVar }; * You can create a new Field from everything "field-like" (`bigint`, integer `number`, decimal `string`, `Field`). * @example * ``` - * Field(10n); // Field contruction from a big integer + * Field(10n); // Field construction from a big integer * Field(100); // Field construction from a number * Field("1"); // Field construction from a decimal string * ``` @@ -141,40 +93,29 @@ class Field { * Coerce anything "field-like" (bigint, number, string, and {@link Field}) to a Field. */ constructor(x: bigint | number | string | Field | FieldVar | FieldConst) { - if (Field.#isField(x)) { + if (x instanceof Field) { this.value = x.value; return; } - // FieldVar if (Array.isArray(x)) { - this.value = x; - return; - } - // FieldConst - if (x instanceof Uint8Array) { - this.value = FieldVar.constant(x); - return; + if (typeof x[1] === 'bigint') { + // FieldConst + this.value = FieldVar.constant(x as FieldConst); + return; + } else { + // FieldVar + this.value = x as FieldVar; + return; + } } // TODO this should handle common values efficiently by reading from a lookup table - this.value = FieldVar.constant(Fp(x)); + this.value = FieldVar.constant(Fp.mod(BigInt(x))); } // helpers - static #isField( - x: bigint | number | string | Field | FieldVar | FieldConst - ): x is Field { - return x instanceof Field; - } - static #toConst(x: bigint | number | string | ConstantField): FieldConst { - if (Field.#isField(x)) return x.value[1]; - return FieldConst.fromBigint(Fp(x)); - } - static #toVar(x: bigint | number | string | Field): FieldVar { - if (Field.#isField(x)) return x.value; - return FieldVar.constant(Fp(x)); - } + static from(x: bigint | number | string | Field): Field { - if (Field.#isField(x)) return x; + if (x instanceof Field) return x; return new Field(x); } @@ -200,10 +141,6 @@ class Field { return this.value[0] === FieldType.Constant; } - #toConstant(name: string): ConstantField { - return toConstantField(this, name, 'x', 'field element'); - } - /** * Create a {@link Field} element equivalent to this {@link Field} element's value, * but is a constant. @@ -218,7 +155,7 @@ class Field { * @return A constant {@link Field} element equivalent to this {@link Field} element. */ toConstant(): ConstantField { - return this.#toConstant('toConstant'); + return toConstant(this, 'toConstant'); } /** @@ -235,7 +172,7 @@ class Field { * @return A bigint equivalent to the bigint representation of the Field. */ toBigInt() { - let x = this.#toConstant('toBigInt'); + let x = toConstant(this, 'toBigInt'); return FieldConst.toBigint(x.value[1]); } @@ -253,7 +190,7 @@ class Field { * @return A string equivalent to the string representation of the Field. */ toString() { - return this.#toConstant('toString').toBigInt().toString(); + return toConstant(this, 'toString').toBigInt().toString(); } /** @@ -274,7 +211,7 @@ class Field { } return; } - Snarky.field.assertEqual(this.value, Field.#toVar(y)); + assertEqual(this, toFieldVar(y)); } catch (err) { throw withMessage(err, message); } @@ -298,7 +235,7 @@ class Field { * const sum = x.add(Field(-7)); * * // If you try to print sum - `console.log(sum.toBigInt())` - you will realize that it prints a very big integer because this is modular arithmetic, and 1 + (-7) circles around the field to become p - 6. - * // You can use the reverse operation of addition (substraction) to prove the sum is calculated correctly. + * // You can use the reverse operation of addition (subtraction) to prove the sum is calculated correctly. * * sum.sub(x).assertEquals(Field(-7)); * sum.sub(Field(-7)).assertEquals(x); @@ -313,7 +250,7 @@ class Field { return new Field(Fp.add(this.toBigInt(), toFp(y))); } // return new AST node Add(x, y) - let z = Snarky.field.add(this.value, Field.#toVar(y)); + let z = FieldVar.add(this.value, toFieldVar(y)); return new Field(z); } @@ -341,12 +278,12 @@ class Field { return new Field(Fp.negate(this.toBigInt())); } // return new AST node Scale(-1, x) - let z = Snarky.field.scale(FieldConst[-1], this.value); + let z = FieldVar.scale(FieldConst[-1], this.value); return new Field(z); } /** - * Substract another "field-like" value from this {@link Field} element. + * Subtract another "field-like" value from this {@link Field} element. * * @example * ```ts @@ -356,7 +293,7 @@ class Field { * difference.assertEquals(Field(-2)); * ``` * - * **Warning**: This is a modular substraction in the pasta field. + * **Warning**: This is a modular subtraction in the pasta field. * * @example * ```ts @@ -364,11 +301,11 @@ class Field { * const difference = x.sub(Field(2)); * * // If you try to print difference - `console.log(difference.toBigInt())` - you will realize that it prints a very big integer because this is modular arithmetic, and 1 - 2 circles around the field to become p - 1. - * // You can use the reverse operation of substraction (addition) to prove the difference is calculated correctly. + * // You can use the reverse operation of subtraction (addition) to prove the difference is calculated correctly. * difference.add(Field(2)).assertEquals(x); * ``` * - * @param value - a "field-like" value to substract from the {@link Field}. + * @param value - a "field-like" value to subtract from the {@link Field}. * * @return A {@link Field} element equivalent to the modular difference of the two value. */ @@ -376,6 +313,32 @@ class Field { return this.add(Field.from(y).neg()); } + /** + * Checks if this {@link Field} is odd. Returns `true` for odd elements and `false` for even elements. + * + * See {@link Field.isEven} for examples. + */ + isOdd() { + if (this.isConstant()) return new Bool((this.toBigInt() & 1n) === 1n); + + // witness a bit b such that x = b + 2z for some z <= (p-1)/2 + // this is always possible, and unique _except_ in the edge case where x = 0 = 0 + 2*0 = 1 + 2*(p-1)/2 + // so we can compute isOdd = b AND (x != 0) + let [b, z] = exists(2, () => { + let x = this.toBigInt(); + return [x & 1n, x >> 1n]; + }); + let isOdd = b.assertBool(); + z.assertLessThan((Field.ORDER + 1n) / 2n); + + // x == b + 2z + b.add(z.mul(2)).assertEquals(this); + + // avoid overflow case when x = 0 + let isNonZero = this.equals(0).not(); + return isOdd.and(isNonZero); + } + /** * Checks if this {@link Field} is even. Returns `true` for even elements and `false` for odd elements. * @@ -383,40 +346,13 @@ class Field { * ```ts * let a = Field(5); * a.isEven(); // false - * a.isEven().assertTrue(); // throws, as expected! * * let b = Field(4); * b.isEven(); // true - * b.isEven().assertTrue(); // does not throw, as expected! * ``` */ isEven() { - if (this.isConstant()) return new Bool(this.toBigInt() % 2n === 0n); - - let [, isOddVar, xDiv2Var] = Snarky.exists(2, () => { - let bits = Fp.toBits(this.toBigInt()); - let isOdd = bits.shift()! ? 1n : 0n; - - return [ - 0, - FieldConst.fromBigint(isOdd), - FieldConst.fromBigint(Fp.fromBits(bits)), - ]; - }); - - let isOdd = new Field(isOddVar); - let xDiv2 = new Field(xDiv2Var); - - // range check for 253 bits - // WARNING: this makes use of a special property of the Pasta curves, - // namely that a random field element is < 2^254 with overwhelming probability - // TODO use 88-bit RCs to make this more efficient - xDiv2.toBits(253); - - // check composition - xDiv2.mul(2).add(isOdd).assertEquals(this); - - return new Bool(isOddVar).not(); + return this.isOdd().not(); } /** @@ -440,20 +376,19 @@ class Field { } // if one of the factors is constant, return Scale AST node if (isConstant(y)) { - let z = Snarky.field.scale(Field.#toConst(y), this.value); + let z = FieldVar.scale(toFieldConst(y), this.value); return new Field(z); } if (this.isConstant()) { - let z = Snarky.field.scale(this.value[1], y.value); + let z = FieldVar.scale(this.value[1], y.value); return new Field(z); } // create a new witness for z = x*y - let z = Snarky.existsVar(() => - FieldConst.fromBigint(Fp.mul(this.toBigInt(), toFp(y))) - ); + let z = existsOne(() => Fp.mul(this.toBigInt(), toFp(y))); + // add a multiplication constraint - Snarky.field.assertMul(this.value, y.value, z); - return new Field(z); + assertMul(this, y, z); + return z; } /** @@ -480,13 +415,11 @@ class Field { return new Field(z); } // create a witness for z = x^(-1) - let z = Snarky.existsVar(() => { - let z = Fp.inverse(this.toBigInt()) ?? 0n; - return FieldConst.fromBigint(z); - }); + let z = existsOne(() => Fp.inverse(this.toBigInt()) ?? 0n); + // constrain x * z === 1 - Snarky.field.assertMul(this.value, z, FieldVar[1]); - return new Field(z); + assertMul(this, z, FieldVar[1]); + return z; } /** @@ -522,7 +455,8 @@ class Field { * @return A {@link Field} element equivalent to the modular division of the two value. */ div(y: Field | bigint | number | string) { - // TODO this is the same as snarky-ml but could use 1 constraint instead of 2 + // this intentionally uses 2 constraints instead of 1 to avoid an unconstrained output when dividing 0/0 + // (in this version, division by 0 is strictly not allowed) return this.mul(Field.from(y).inv()); } @@ -546,12 +480,11 @@ class Field { return new Field(Fp.square(this.toBigInt())); } // create a new witness for z = x^2 - let z = Snarky.existsVar(() => - FieldConst.fromBigint(Fp.square(this.toBigInt())) - ); + let z = existsOne(() => Fp.square(this.toBigInt())); + // add a squaring constraint - Snarky.field.assertSquare(this.value, z); - return new Field(z); + assertSquare(this, z); + return z; } /** @@ -581,43 +514,11 @@ class Field { return new Field(z); } // create a witness for sqrt(x) - let z = Snarky.existsVar(() => { - let z = Fp.sqrt(this.toBigInt()) ?? 0n; - return FieldConst.fromBigint(z); - }); - // constrain z * z === x - Snarky.field.assertSquare(z, this.value); - return new Field(z); - } + let z = existsOne(() => Fp.sqrt(this.toBigInt()) ?? 0n); - /** - * @deprecated use `x.equals(0)` which is equivalent - */ - isZero() { - if (this.isConstant()) { - return new Bool(this.toBigInt() === 0n); - } - // create witnesses z = 1/x, or z=0 if x=0, - // and b = 1 - zx - let [, b, z] = Snarky.exists(2, () => { - let x = this.toBigInt(); - let z = Fp.inverse(x) ?? 0n; - let b = Fp.sub(1n, Fp.mul(z, x)); - return [0, FieldConst.fromBigint(b), FieldConst.fromBigint(z)]; - }); - // add constraints - // b * x === 0 - Snarky.field.assertMul(b, this.value, FieldVar[0]); - // z * x === 1 - b - Snarky.field.assertMul( - z, - this.value, - Snarky.field.add(FieldVar[1], Snarky.field.scale(FieldConst[-1], b)) - ); - // ^^^ these prove that b = Bool(x === 0): - // if x = 0, the 2nd equation implies b = 1 - // if x != 0, the 1st implies b = 0 - return Bool.Unsafe.ofField(new Field(b)); + // constrain z * z === x + assertSquare(z, this); + return z; } /** @@ -634,43 +535,32 @@ class Field { * @return A {@link Bool} representing if this {@link Field} is equal another "field-like" value. */ equals(y: Field | bigint | number | string): Bool { - // x == y is equivalent to x - y == 0 - // TODO: this is less efficient than possible for equivalence with snarky-ml - return this.sub(y).isZero(); - // more efficient code is commented below - /* - // if one of the two is constant, we just need the two constraints in `isZero` - if (this.isConstant() || isConstant(y)) { - return this.sub(y).isZero(); + if (this.isConstant() && isConstant(y)) { + return new Bool(this.toBigInt() === toFp(y)); } - // if both are variables, we create one new variable for x-y so that `isZero` doesn't create two - let xMinusY = Snarky.existsVar(() => - FieldConst.fromBigint(Fp.sub(this.toBigInt(), toFp(y))) - ); - Snarky.field.assertEqual(this.sub(y).value, xMinusY); - return new Field(xMinusY).isZero(); - */ - } + // TODO: this wastes a constraint on `xMinusY` if one of them is constant + // to fix, make assertMul() smart about constant terms and only `seal()` if the two inputs are both variables - // internal base method for all comparisons - #compare(y: FieldVar) { - // TODO: support all bit lengths - let maxLength = Fp.sizeInBits - 2; - asProver(() => { - let actualLength = Math.max( - this.toBigInt().toString(2).length, - new Field(y).toBigInt().toString(2).length - ); - if (actualLength > maxLength) - throw Error( - `Provable comparison functions can only be used on Fields of size <= ${maxLength} bits, got ${actualLength} bits.` - ); + // x == y is equivalent to x - y == 0 + let xMinusY = this.sub(y).seal(); + + // create witnesses z = 1/(x-y), or z=0 if x=y, + // and b = 1 - z(x-y) + let [b, z] = exists(2, () => { + let xmy = xMinusY.toBigInt(); + let z = Fp.inverse(xmy) ?? 0n; + let b = Fp.sub(1n, Fp.mul(z, xmy)); + return [b, z]; }); - let [, less, lessOrEqual] = Snarky.field.compare(maxLength, this.value, y); - return { - less: Bool.Unsafe.ofField(new Field(less)), - lessOrEqual: Bool.Unsafe.ofField(new Field(lessOrEqual)), - }; + // add constraints + // b * (x-y) === 0 + assertMul(b, xMinusY, FieldVar[0]); + // z * (x-y) === 1 - b + assertMul(z, xMinusY, new Field(1).sub(b)); + // ^^^ these prove that b = Bool(x === y): + // if x = y, the 2nd equation implies b = 1 + // if x != y, the 1st implies b = 0 + return Bool.Unsafe.fromField(b); } /** @@ -679,17 +569,14 @@ class Field { * * @example * ```ts - * Field(2).lessThan(3).assertEquals(Bool(true)); + * let isTrue = Field(2).lessThan(3); * ``` * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behavior when used with negative inputs or modular division. * * @example * ```ts - * Field(1).div(Field(3)).lessThan(Field(1).div(Field(2))).assertEquals(Bool(true)); // This code will throw an error + * let isFalse = Field(1).div(3).lessThan(Field(1).div(2)); // in fact, 1/3 > 1/2 * ``` * * @param value - the "field-like" value to compare with this {@link Field}. @@ -700,7 +587,7 @@ class Field { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBigInt() < toFp(y)); } - return this.#compare(Field.#toVar(y)).less; + return lessThanFull(this, Field.from(y)); } /** @@ -709,17 +596,14 @@ class Field { * * @example * ```ts - * Field(3).lessThanOrEqual(3).assertEquals(Bool(true)); + * let isTrue = Field(3).lessThanOrEqual(3); * ``` * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behaviour when used with negative inputs or modular division. * * @example * ```ts - * Field(1).div(Field(3)).lessThanOrEqual(Field(1).div(Field(2))).assertEquals(Bool(true)); // This code will throw an error + * let isFalse = Field(1).div(3).lessThanOrEqual(Field(1).div(2)); // in fact, 1/3 > 1/2 * ``` * * @param value - the "field-like" value to compare with this {@link Field}. @@ -730,7 +614,7 @@ class Field { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBigInt() <= toFp(y)); } - return this.#compare(Field.#toVar(y)).lessOrEqual; + return lessThanOrEqualFull(this, Field.from(y)); } /** @@ -739,17 +623,14 @@ class Field { * * @example * ```ts - * Field(5).greaterThan(3).assertEquals(Bool(true)); + * let isTrue = Field(5).greaterThan(3); * ``` * - * **Warning**: Comparison methods currently only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behaviour when used with negative inputs or modular division. * * @example * ```ts - * Field(1).div(Field(2)).greaterThan(Field(1).div(Field(3))).assertEquals(Bool(true)); // This code will throw an error + * let isFalse = Field(1).div(2).greaterThan(Field(1).div(3); // in fact, 1/3 > 1/2 * ``` * * @param value - the "field-like" value to compare with this {@link Field}. @@ -757,8 +638,7 @@ class Field { * @return A {@link Bool} representing if this {@link Field} is greater than another "field-like" value. */ greaterThan(y: Field | bigint | number | string) { - // TODO: this is less efficient than possible for equivalence with ml - return this.lessThanOrEqual(y).not(); + return Field.from(y).lessThan(this); } /** @@ -767,17 +647,14 @@ class Field { * * @example * ```ts - * Field(3).greaterThanOrEqual(3).assertEquals(Bool(true)); + * let isTrue = Field(3).greaterThanOrEqual(3); * ``` * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behaviour when used with negative inputs or modular division. * * @example * ```ts - * Field(1).div(Field(2)).greaterThanOrEqual(Field(1).div(Field(3))).assertEquals(Bool(true)); // This code will throw an error + * let isFalse = Field(1).div(2).greaterThanOrEqual(Field(1).div(3); // in fact, 1/3 > 1/2 * ``` * * @param value - the "field-like" value to compare with this {@link Field}. @@ -785,20 +662,17 @@ class Field { * @return A {@link Bool} representing if this {@link Field} is greater than or equal another "field-like" value. */ greaterThanOrEqual(y: Field | bigint | number | string) { - // TODO: this is less efficient than possible for equivalence with ml - return this.lessThan(y).not(); + return Field.from(y).lessThanOrEqual(this); } /** * Assert that this {@link Field} is less than another "field-like" value. - * Calling this function is equivalent to `Field(...).lessThan(...).assertEquals(Bool(true))`. + * + * Note: This uses fewer constraints than `x.lessThan(y).assertTrue()`. * See {@link Field.lessThan} for more details. * * **Important**: If an assertion fails, the code throws an error. * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ @@ -810,8 +684,7 @@ class Field { } return; } - let { less } = this.#compare(Field.#toVar(y)); - less.assertTrue(); + assertLessThanFull(this, Field.from(y)); } catch (err) { throw withMessage(err, message); } @@ -819,14 +692,12 @@ class Field { /** * Assert that this {@link Field} is less than or equal to another "field-like" value. - * Calling this function is equivalent to `Field(...).lessThanOrEqual(...).assertEquals(Bool(true))`. + * + * Note: This uses fewer constraints than `x.lessThanOrEqual(y).assertTrue()`. * See {@link Field.lessThanOrEqual} for more details. * * **Important**: If an assertion fails, the code throws an error. * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ @@ -838,8 +709,7 @@ class Field { } return; } - let { lessOrEqual } = this.#compare(Field.#toVar(y)); - lessOrEqual.assertTrue(); + assertLessThanOrEqualFull(this, Field.from(y)); } catch (err) { throw withMessage(err, message); } @@ -847,14 +717,12 @@ class Field { /** * Assert that this {@link Field} is greater than another "field-like" value. - * Calling this function is equivalent to `Field(...).greaterThan(...).assertEquals(Bool(true))`. + * + * Note: This uses fewer constraints than `x.greaterThan(y).assertTrue()`. * See {@link Field.greaterThan} for more details. * * **Important**: If an assertion fails, the code throws an error. * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ @@ -864,14 +732,12 @@ class Field { /** * Assert that this {@link Field} is greater than or equal to another "field-like" value. - * Calling this function is equivalent to `Field(...).greaterThanOrEqual(...).assertEquals(Bool(true))`. + * + * Note: This uses fewer constraints than `x.greaterThanOrEqual(y).assertTrue()`. * See {@link Field.greaterThanOrEqual} for more details. * * **Important**: If an assertion fails, the code throws an error. * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ @@ -909,70 +775,69 @@ class Field { } /** - * Assert that this {@link Field} is equal to 1 or 0 as a "field-like" value. - * Calling this function is equivalent to `Bool.or(Field(...).equals(1), Field(...).equals(0)).assertEquals(Bool(true))`. + * Prove that this {@link Field} is equal to 0 or 1. + * Returns the Field wrapped in a {@link Bool}. * - * **Important**: If an assertion fails, the code throws an error. + * If the assertion fails, the code throws an error. * - * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ assertBool(message?: string) { try { if (this.isConstant()) { let x = this.toBigInt(); - if (x !== 0n && x !== 1n) { - throw Error(`Field.assertBool(): expected ${x} to be 0 or 1`); - } - return; + assert( + x === 0n || x === 1n, + `Field.assertBool(): expected ${x} to be 0 or 1` + ); + return new Bool(x === 1n); } - Snarky.field.assertBoolean(this.value); + assertBoolean(this); + return Bool.Unsafe.fromField(this); } catch (err) { throw withMessage(err, message); } } - static #checkBitLength(name: string, length: number) { - if (length > Fp.sizeInBits) - throw Error( - `${name}: bit length must be ${Fp.sizeInBits} or less, got ${length}` - ); - if (length <= 0) - throw Error(`${name}: bit length must be positive, got ${length}`); - } - /** * Returns an array of {@link Bool} elements representing [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) of this {@link Field} element. * * If you use the optional `length` argument, proves that the field element fits in `length` bits. - * The `length` has to be between 0 and 255 and the method throws if it isn't. + * The `length` has to be between 0 and 254 and the method throws if it isn't. * * **Warning**: The cost of this operation in a zk proof depends on the `length` you specify, - * which by default is 255 bits. Prefer to pass a smaller `length` if possible. + * which by default is 254 bits. Prefer to pass a smaller `length` if possible. * * @param length - the number of bits to fit the element. If the element does not fit in `length` bits, the functions throws an error. * * @return An array of {@link Bool} element representing little endian binary representation of this {@link Field}. */ - toBits(length?: number) { - if (length !== undefined) Field.#checkBitLength('Field.toBits()', length); + toBits(length: number = 254) { + checkBitLength('Field.toBits()', length, 254); if (this.isConstant()) { - let bits = Fp.toBits(this.toBigInt()); - if (length !== undefined) { - if (bits.slice(length).some((bit) => bit)) - throw Error(`Field.toBits(): ${this} does not fit in ${length} bits`); - return bits.slice(0, length).map((b) => new Bool(b)); - } - return bits.map((b) => new Bool(b)); + let bits = BinableFp.toBits(this.toBigInt()); + if (bits.slice(length).some((bit) => bit)) + throw Error(`Field.toBits(): ${this} does not fit in ${length} bits`); + return bits.slice(0, length).map((b) => new Bool(b)); } - let [, ...bits] = Snarky.field.toBits(length ?? Fp.sizeInBits, this.value); - return bits.map((b) => Bool.Unsafe.ofField(new Field(b))); + let bits = Provable.witness(Provable.Array(Bool, length), () => { + let f = this.toBigInt(); + return Array.from( + { length }, + (_, k) => new Bool(!!((f >> BigInt(k)) & 0x1n)) + ); + }); + Field.fromBits(bits).assertEquals( + this, + `Field.toBits(): Input does not fit in ${length} bits` + ); + return bits; } /** * Convert a bit array into a {@link Field} element using [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) * - * The method throws if the given bits do not fit in a single Field element. A Field element can be at most 255 bits. + * The method throws if the given bits do not fit in a single Field element. In this case, no more than 254 bits are allowed because some 255 bit integers do not fit into a single Field element. * * **Important**: If the given `bytes` array is an array of `booleans` or {@link Bool} elements that all are `constant`, the resulting {@link Field} element will be a constant as well. Or else, if the given array is a mixture of constants and variables of {@link Bool} type, the resulting {@link Field} will be a variable as well. * @@ -981,68 +846,44 @@ class Field { * @return A {@link Field} element matching the [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) of the given `bytes` array. */ static fromBits(bits: (Bool | boolean)[]) { - let length = bits.length; - Field.#checkBitLength('Field.fromBits()', length); + const length = bits.length; + checkBitLength('Field.fromBits()', length, 254); if (bits.every((b) => typeof b === 'boolean' || b.toField().isConstant())) { let bits_ = bits .map((b) => (typeof b === 'boolean' ? b : b.toBoolean())) .concat(Array(Fp.sizeInBits - length).fill(false)); - return new Field(Fp.fromBits(bits_)); - } - let bitsVars = bits.map((b): FieldVar => { - if (typeof b === 'boolean') return b ? FieldVar[1] : FieldVar[0]; - return b.toField().value; - }); - let x = Snarky.field.fromBits([0, ...bitsVars]); - return new Field(x); - } - - /** - * Create a new {@link Field} element from the first `length` bits of this {@link Field} element. - * - * The `length` has to be a multiple of 16, and has to be between 0 and 255, otherwise the method throws. - * - * As {@link Field} elements are represented using [little endian binary representation](https://en.wikipedia.org/wiki/Endianness), - * the resulting {@link Field} element will equal the original one if it fits in `length` bits. - * - * @param length - The number of bits to take from this {@link Field} element. - * - * @return A {@link Field} element that is equal to the `length` of this {@link Field} element. - */ - rangeCheckHelper(length: number) { - Field.#checkBitLength('Field.rangeCheckHelper()', length); - if (length % 16 !== 0) - throw Error( - 'Field.rangeCheckHelper(): `length` has to be a multiple of 16.' - ); - let lengthDiv16 = length / 16; - if (this.isConstant()) { - let bits = Fp.toBits(this.toBigInt()) - .slice(0, length) - .concat(Array(Fp.sizeInBits - length).fill(false)); - return new Field(Fp.fromBits(bits)); + return new Field(BinableFp.fromBits(bits_)); } - let x = Snarky.field.truncateToBits16(lengthDiv16, this.value); - return new Field(x); + return bits + .map((b) => new Bool(b)) + .reduce((acc, bit, idx) => { + const shift = 1n << BigInt(idx); + return acc.add(bit.toField().mul(shift)); + }, Field.from(0)) + .seal(); } /** * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. * - * In SnarkyJS, addition and scaling (multiplication of variables by a constant) of variables is represented as an AST - [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree). For example, the expression `x.add(y).mul(2)` is represented as `Scale(2, Add(x, y))`. + * In o1js, addition and scaling (multiplication of variables by a constant) of variables is represented as an AST - [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree). For example, the expression `x.add(y).mul(2)` is represented as `Scale(2, Add(x, y))`. * * A new internal variable is created only when the variable is needed in a multiplicative or any higher level constraint (for example multiplication of two {@link Field} elements) to represent the operation. * - * The `seal()` function tells SnarkyJS to stop building an AST and create a new variable right away. + * The `seal()` function tells o1js to stop building an AST and create a new variable right away. * * @return A {@link Field} element that is equal to the result of AST that was previously on this {@link Field} element. */ seal() { - // TODO: this is just commented for constraint equivalence with the old version - // uncomment to sometimes save constraints - // if (this.isConstant()) return this; - let x = Snarky.field.seal(this.value); - return new Field(x); + let { constant, terms } = toLinearCombination(this.value); + if (terms.length === 0) return new Field(constant); + if (terms.length === 1 && constant === 0n) { + let [c, x] = terms[0]; + if (c === 1n) return new Field(x); + } + let x = existsOne(() => this.toBigInt()); + this.assertEquals(x); + return x; } /** @@ -1151,6 +992,10 @@ class Field { // ProvableExtended + static empty() { + return new Field(0n); + } + /** * Serialize the {@link Field} to a JSON string, e.g. for printing. Trying to print a {@link Field} without this function will directly stringify the Field object, resulting in unreadable output. * @@ -1165,7 +1010,7 @@ class Field { * @return A string equivalent to the JSON representation of the {@link Field}. */ toJSON() { - return this.#toConstant('toJSON').toString(); + return toConstant(this, 'toJSON').toString(); } /** @@ -1197,7 +1042,7 @@ class Field { * @return A {@link Field} coerced from the given JSON string. */ static fromJSON(json: string) { - return new Field(Fp.fromJSON(json)); + return new Field(SignableFp.fromJSON(json)); } /** @@ -1256,31 +1101,31 @@ class Field { } /** - * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. - * - * As all {@link Field} elements have 31 bits, this function returns 31. - * - * @return The size of a {@link Field} element - 31. + * The size of a {@link Field} element in bytes - 32. */ - static sizeInBytes() { - return Fp.sizeInBytes(); - } + static sizeInBytes = BinableFp.sizeInBytes; + + /** + * The size of a {@link Field} element in bits - 255. + */ + static sizeInBits = Fp.sizeInBits; } +setFieldConstructor(Field); const FieldBinable = defineBinable({ toBytes(t: Field) { - return [...toConstantField(t, 'toBytes').value[1]]; + let t0 = toConstantField(t, 'toBytes').toBigInt(); + return BinableFp.toBytes(t0); }, readBytes(bytes, offset) { let uint8array = new Uint8Array(32); uint8array.set(bytes.slice(offset, offset + 32)); - return [new Field(uint8array), offset + 32]; + let x = BinableFp.fromBytes([...uint8array]); + return [new Field(x), offset + 32]; }, }); -function isField(x: unknown): x is Field { - return x instanceof Field; -} +// internal helper functions function isConstant( x: bigint | number | string | Field @@ -1292,20 +1137,47 @@ function isConstant( return (x as Field).isConstant(); } -function toFp(x: bigint | number | string | Field): Fp { +function toFp(x: bigint | number | string | Field): bigint { let type = typeof x; if (type === 'bigint' || type === 'number' || type === 'string') { - return Fp(x as bigint | number | string); + return Fp.mod(BigInt(x as bigint | number | string)); } return (x as Field).toBigInt(); } +function toFieldConst(x: bigint | number | string | ConstantField): FieldConst { + if (x instanceof Field) return x.value[1]; + return FieldConst.fromBigint(Fp.mod(BigInt(x))); +} + +function toFieldVar(x: bigint | number | string | Field): FieldVar { + if (x instanceof Field) return x.value; + return FieldVar.constant(Fp.mod(BigInt(x))); +} + function withMessage(error: unknown, message?: string) { if (message === undefined || !(error instanceof Error)) return error; error.message = `${message}\n${error.message}`; return error; } +function checkBitLength( + name: string, + length: number, + maxLength = Fp.sizeInBits +) { + if (length > maxLength) + throw Error( + `${name}: bit length must be ${maxLength} or less, got ${length}` + ); + if (length < 0) + throw Error(`${name}: bit length must be non-negative, got ${length}`); +} + +function toConstant(x: Field, name: string): ConstantField { + return toConstantField(x, name, 'x', 'field element'); +} + function toConstantField( x: Field, methodName: string, @@ -1350,3 +1222,7 @@ there is \`Provable.asProver(() => { ... })\` which allows you to use ${varName} Warning: whatever happens inside asProver() will not be part of the zk proof. `; } + +function VarField(x: VarFieldVar): VarField { + return new Field(x) as VarField; +} diff --git a/src/lib/provable/foreign-field.ts b/src/lib/provable/foreign-field.ts new file mode 100644 index 0000000000..e9f658682b --- /dev/null +++ b/src/lib/provable/foreign-field.ts @@ -0,0 +1,739 @@ +import { + mod, + Fp, + FiniteField, + createField, +} from '../../bindings/crypto/finite-field.js'; +import { Field, checkBitLength, withMessage } from './field.js'; +import { Provable } from './provable.js'; +import { Bool } from './bool.js'; +import { Tuple, TupleMap, TupleN } from '../util/types.js'; +import { Field3 } from './gadgets/foreign-field.js'; +import { Gadgets } from './gadgets/gadgets.js'; +import { ForeignField as FF } from './gadgets/foreign-field.js'; +import { assert } from './gadgets/common.js'; +import { l3, l } from './gadgets/range-check.js'; +import { ProvablePureExtended } from './types/struct.js'; + +// external API +export { createForeignField }; +export type { + ForeignField, + UnreducedForeignField, + AlmostForeignField, + CanonicalForeignField, +}; + +class ForeignField { + static _Bigint: FiniteField | undefined = undefined; + static _modulus: bigint | undefined = undefined; + + // static parameters + static get Bigint() { + assert(this._Bigint !== undefined, 'ForeignField class not initialized.'); + return this._Bigint; + } + static get modulus() { + assert(this._modulus !== undefined, 'ForeignField class not initialized.'); + return this._modulus; + } + get modulus() { + return (this.constructor as typeof ForeignField).modulus; + } + static get sizeInBits() { + return this.modulus.toString(2).length; + } + + /** + * The internal representation of a foreign field element, as a tuple of 3 limbs. + */ + value: Field3; + + get Constructor() { + return this.constructor as typeof ForeignField; + } + + /** + * Sibling classes that represent different ranges of field elements. + */ + static _variants: + | { + unreduced: typeof UnreducedForeignField; + almostReduced: typeof AlmostForeignField; + canonical: typeof CanonicalForeignField; + } + | undefined = undefined; + + /** + * Constructor for unreduced field elements. + */ + static get Unreduced() { + assert(this._variants !== undefined, 'ForeignField class not initialized.'); + return this._variants.unreduced; + } + /** + * Constructor for field elements that are "almost reduced", i.e. lie in the range [0, 2^ceil(log2(p))). + */ + static get AlmostReduced() { + assert(this._variants !== undefined, 'ForeignField class not initialized.'); + return this._variants.almostReduced; + } + /** + * Constructor for field elements that are fully reduced, i.e. lie in the range [0, p). + */ + static get Canonical() { + assert(this._variants !== undefined, 'ForeignField class not initialized.'); + return this._variants.canonical; + } + + /** + * Create a new {@link ForeignField} from a bigint, number, string or another ForeignField. + * @example + * ```ts + * let x = new ForeignField(5); + * ``` + */ + constructor(x: ForeignField | Field3 | bigint | number | string) { + const p = this.modulus; + if (x instanceof ForeignField) { + this.value = x.value; + return; + } + // Field3 + if (Array.isArray(x)) { + this.value = x; + return; + } + // constant + this.value = Field3.from(mod(BigInt(x), p)); + } + + /** + * Coerce the input to a {@link ForeignField}. + */ + static from(x: bigint | number | string): CanonicalForeignField; + static from(x: ForeignField | bigint | number | string): ForeignField; + static from(x: ForeignField | bigint | number | string): ForeignField { + if (x instanceof this) return x; + return new this.Canonical(x); + } + + /** + * Checks whether this field element is a constant. + * + * See {@link FieldVar} to understand constants vs variables. + */ + isConstant() { + return Field3.isConstant(this.value); + } + + /** + * Convert this field element to a constant. + * + * See {@link FieldVar} to understand constants vs variables. + * + * **Warning**: This function is only useful in {@link Provable.witness} or {@link Provable.asProver} blocks, + * that is, in situations where the prover computes a value outside provable code. + */ + toConstant(): ForeignField { + let constantLimbs = Tuple.map(this.value, (l) => l.toConstant()); + return new this.Constructor(constantLimbs); + } + + /** + * Convert this field element to a bigint. + */ + toBigInt() { + return Field3.toBigint(this.value); + } + + /** + * Assert that this field element lies in the range [0, 2^k), + * where k = ceil(log2(p)) and p is the foreign field modulus. + * + * Returns the field element as a {@link AlmostForeignField}. + * + * For a more efficient version of this for multiple field elements, see {@link assertAlmostReduced}. + * + * Note: this does not ensure that the field elements is in the canonical range [0, p). + * To assert that stronger property, there is {@link assertCanonical}. + * You should typically use {@link assertAlmostReduced} though, because it is cheaper to prove and sufficient for + * ensuring validity of all our non-native field arithmetic methods. + */ + assertAlmostReduced() { + // TODO: this is not very efficient, but the only way to abstract away the complicated + // range check assumptions and also not introduce a global context of pending range checks. + // we plan to get rid of bounds checks anyway, then this is just a multi-range check + let [x] = this.Constructor.assertAlmostReduced(this); + return x; + } + + /** + * Assert that one or more field elements lie in the range [0, 2^k), + * where k = ceil(log2(p)) and p is the foreign field modulus. + * + * This is most efficient than when checking a multiple of 3 field elements at once. + */ + static assertAlmostReduced>( + ...xs: T + ): TupleMap { + Gadgets.ForeignField.assertAlmostReduced( + xs.map((x) => x.value), + this.modulus, + { skipMrc: true } + ); + return Tuple.map(xs, this.AlmostReduced.unsafeFrom); + } + + /** + * Assert that this field element is fully reduced, + * i.e. lies in the range [0, p), where p is the foreign field modulus. + * + * Returns the field element as a {@link CanonicalForeignField}. + */ + assertCanonical() { + this.assertLessThan(this.modulus); + return this.Constructor.Canonical.unsafeFrom(this); + } + + // arithmetic with full constraints, for safe use + + /** + * Finite field addition + * @example + * ```ts + * x.add(2); // x + 2 mod p + * ``` + */ + add(y: ForeignField | bigint | number) { + return this.Constructor.sum([this, y], [1]); + } + + /** + * Finite field negation + * @example + * ```ts + * x.neg(); // -x mod p = p - x + * ``` + */ + neg() { + // this gets a special implementation because negation proves that the return value is almost reduced. + // it shows that r = f - x >= 0 or r = 0 (for x=0) over the integers, which implies r < f + // see also `Gadgets.ForeignField.assertLessThan()` + let xNeg = Gadgets.ForeignField.neg(this.value, this.modulus); + return new this.Constructor.AlmostReduced(xNeg); + } + + /** + * Finite field subtraction + * @example + * ```ts + * x.sub(1); // x - 1 mod p + * ``` + */ + sub(y: ForeignField | bigint | number) { + return this.Constructor.sum([this, y], [-1]); + } + + /** + * Sum (or difference) of multiple finite field elements. + * + * @example + * ```ts + * let z = ForeignField.sum([3, 2, 1], [-1, 1]); // 3 - 2 + 1 + * z.assertEquals(2); + * ``` + * + * This method expects a list of ForeignField-like values, `x0,...,xn`, + * and a list of "operations" `op1,...,opn` where every op is 1 or -1 (plus or minus), + * and returns + * + * `x0 + op1*x1 + ... + opn*xn` + * + * where the sum is computed in finite field arithmetic. + * + * **Important:** For more than two summands, this is significantly more efficient + * than chaining calls to {@link ForeignField.add} and {@link ForeignField.sub}. + * + */ + static sum(xs: (ForeignField | bigint | number)[], operations: (1 | -1)[]) { + const p = this.modulus; + let fields = xs.map((x) => toLimbs(x, p)); + let ops = operations.map((op) => (op === 1 ? 1n : -1n)); + let z = Gadgets.ForeignField.sum(fields, ops, p); + return new this.Unreduced(z); + } + + // convenience methods + + /** + * Assert equality with a ForeignField-like value + * + * @example + * ```ts + * x.assertEquals(0, "x is zero"); + * ``` + * + * Since asserting equality can also serve as a range check, + * this method returns `x` with the appropriate type: + * + * @example + * ```ts + * let xChecked = x.assertEquals(1, "x is 1"); + * xChecked satisfies CanonicalForeignField; + * ``` + */ + assertEquals( + y: bigint | number | CanonicalForeignField, + message?: string + ): CanonicalForeignField; + assertEquals(y: AlmostForeignField, message?: string): AlmostForeignField; + assertEquals(y: ForeignField, message?: string): ForeignField; + assertEquals( + y: ForeignField | bigint | number, + message?: string + ): ForeignField { + const p = this.modulus; + try { + if (this.isConstant() && isConstant(y)) { + let x = this.toBigInt(); + let y0 = mod(toBigInt(y), p); + if (x !== y0) { + throw Error(`ForeignField.assertEquals(): ${x} != ${y0}`); + } + return new this.Constructor.Canonical(this.value); + } + Provable.assertEqual( + this.Constructor.provable, + this, + new this.Constructor(y) + ); + if (isConstant(y) || y instanceof this.Constructor.Canonical) { + return new this.Constructor.Canonical(this.value); + } else if (y instanceof this.Constructor.AlmostReduced) { + return new this.Constructor.AlmostReduced(this.value); + } else { + return this; + } + } catch (err) { + throw withMessage(err, message); + } + } + + /** + * Assert that this field element is less than a constant c: `x < c`. + * + * The constant must satisfy `0 <= c < 2^264`, otherwise an error is thrown. + * + * @example + * ```ts + * x.assertLessThan(10); + * ``` + */ + assertLessThan(c: bigint | number, message?: string) { + assert( + c >= 0 && c < 1n << l3, + `ForeignField.assertLessThan(): expected c <= c < 2^264, got ${c}` + ); + try { + Gadgets.ForeignField.assertLessThan(this.value, toBigInt(c)); + } catch (err) { + throw withMessage(err, message); + } + } + + // bit packing + + /** + * Unpack a field element to its bits, as a {@link Bool}[] array. + * + * This method is provable! + */ + toBits(length?: number) { + const sizeInBits = this.Constructor.sizeInBits; + if (length === undefined) length = sizeInBits; + checkBitLength('ForeignField.toBits()', length, sizeInBits); + let [l0, l1, l2] = this.value; + let limbSize = Number(l); + let xBits = l0.toBits(Math.min(length, limbSize)); + length -= limbSize; + if (length <= 0) return xBits; + let yBits = l1.toBits(Math.min(length, limbSize)); + length -= limbSize; + if (length <= 0) return [...xBits, ...yBits]; + let zBits = l2.toBits(Math.min(length, limbSize)); + return [...xBits, ...yBits, ...zBits]; + } + + /** + * Create a field element from its bits, as a `Bool[]` array. + * + * This method is provable! + */ + static fromBits(bits: Bool[]) { + let length = bits.length; + checkBitLength('ForeignField.fromBits()', length, this.sizeInBits); + let limbSize = Number(l); + let l0 = Field.fromBits(bits.slice(0 * limbSize, 1 * limbSize)); + let l1 = Field.fromBits(bits.slice(1 * limbSize, 2 * limbSize)); + let l2 = Field.fromBits(bits.slice(2 * limbSize, 3 * limbSize)); + // note: due to the check on the number of bits, we know we return an "almost valid" field element + return new this.AlmostReduced([l0, l1, l2]); + } + + static random() { + return new this.Canonical(this.Bigint.random()); + } + + /** + * Instance version of `Provable.toFields`, see {@link Provable.toFields} + */ + toFields(): Field[] { + return this.value; + } + + static check(_: ForeignField) { + throw Error('ForeignField.check() not implemented: must use a subclass'); + } + + static _provable: any = undefined; + + /** + * `Provable`, see {@link Provable} + */ + static get provable() { + assert(this._provable !== undefined, 'ForeignField class not initialized.'); + return this._provable; + } +} + +class ForeignFieldWithMul extends ForeignField { + /** + * Finite field multiplication + * @example + * ```ts + * x.mul(y); // x*y mod p + * ``` + */ + mul(y: AlmostForeignField | bigint | number) { + const p = this.modulus; + let z = Gadgets.ForeignField.mul(this.value, toLimbs(y, p), p); + return new this.Constructor.Unreduced(z); + } + + /** + * Multiplicative inverse in the finite field + * @example + * ```ts + * let z = x.inv(); // 1/x mod p + * z.mul(x).assertEquals(1); + * ``` + */ + inv() { + const p = this.modulus; + let z = Gadgets.ForeignField.inv(this.value, p); + return new this.Constructor.AlmostReduced(z); + } + + /** + * Division in the finite field, i.e. `x*y^(-1) mod p` where `y^(-1)` is the finite field inverse. + * @example + * ```ts + * let z = x.div(y); // x/y mod p + * z.mul(y).assertEquals(x); + * ``` + */ + div(y: AlmostForeignField | bigint | number) { + const p = this.modulus; + let z = Gadgets.ForeignField.div(this.value, toLimbs(y, p), p); + return new this.Constructor.AlmostReduced(z); + } +} + +class UnreducedForeignField extends ForeignField { + type: 'Unreduced' | 'AlmostReduced' | 'FullyReduced' = 'Unreduced'; + + static _provable: + | ProvablePureExtended + | undefined = undefined; + static get provable() { + assert(this._provable !== undefined, 'ForeignField class not initialized.'); + return this._provable; + } + + static check(x: ForeignField) { + Gadgets.multiRangeCheck(x.value); + } +} + +class AlmostForeignField extends ForeignFieldWithMul { + type: 'AlmostReduced' | 'FullyReduced' = 'AlmostReduced'; + + constructor(x: AlmostForeignField | Field3 | bigint | number | string) { + super(x); + } + + static _provable: + | ProvablePureExtended + | undefined = undefined; + static get provable() { + assert(this._provable !== undefined, 'ForeignField class not initialized.'); + return this._provable; + } + + static check(x: ForeignField) { + Gadgets.multiRangeCheck(x.value); + x.assertAlmostReduced(); + } + + /** + * Coerce the input to an {@link AlmostForeignField} without additional assertions. + * + * **Warning:** Only use if you know what you're doing. + */ + static unsafeFrom(x: ForeignField) { + return new this(x.value); + } + + /** + * Check equality with a constant value. + * + * @example + * ```ts + * let isXZero = x.equals(0); + * ``` + */ + equals(y: bigint | number) { + return FF.equals(this.value, BigInt(y), this.modulus); + } +} + +class CanonicalForeignField extends ForeignFieldWithMul { + type = 'FullyReduced' as const; + + constructor(x: CanonicalForeignField | Field3 | bigint | number | string) { + super(x); + } + + static _provable: + | ProvablePureExtended + | undefined = undefined; + static get provable() { + assert(this._provable !== undefined, 'ForeignField class not initialized.'); + return this._provable; + } + + static check(x: ForeignField) { + Gadgets.multiRangeCheck(x.value); + x.assertCanonical(); + } + + /** + * Coerce the input to a {@link CanonicalForeignField} without additional assertions. + * + * **Warning:** Only use if you know what you're doing. + */ + static unsafeFrom(x: ForeignField) { + return new this(x.value); + } + + /** + * Check equality with a ForeignField-like value. + * + * @example + * ```ts + * let isEqual = x.equals(y); + * ``` + * + * Note: This method only exists on canonical fields; on unreduced fields, it would be easy to + * misuse, because not being exactly equal does not imply being unequal modulo p. + */ + equals(y: CanonicalForeignField | bigint | number) { + let [x0, x1, x2] = this.value; + let [y0, y1, y2] = toLimbs(y, this.modulus); + let x01 = x0.add(x1.mul(1n << l)).seal(); + let y01 = y0.add(y1.mul(1n << l)).seal(); + return x01.equals(y01).and(x2.equals(y2)); + } +} + +function toLimbs( + x: bigint | number | string | ForeignField, + p: bigint +): Field3 { + if (x instanceof ForeignField) return x.value; + return Field3.from(mod(BigInt(x), p)); +} + +function toBigInt(x: bigint | string | number | ForeignField) { + if (x instanceof ForeignField) return x.toBigInt(); + return BigInt(x); +} + +function isConstant(x: bigint | number | string | ForeignField) { + if (x instanceof ForeignField) return x.isConstant(); + return true; +} + +/** + * Create a class representing a prime order finite field, which is different from the native {@link Field}. + * + * ```ts + * const SmallField = createForeignField(17n); // the finite field F_17 + * ``` + * + * `createForeignField(p)` takes the prime modulus `p` of the finite field as input, as a bigint. + * We support prime moduli up to a size of 259 bits. + * + * The returned {@link ForeignField} class supports arithmetic modulo `p` (addition and multiplication), + * as well as helper methods like `assertEquals()` and `equals()`. + * + * _Advanced details:_ + * + * Internally, a foreign field element is represented as three native field elements, each of which + * represents a limb of 88 bits. Therefore, being a valid foreign field element means that all 3 limbs + * fit in 88 bits, and the foreign field element altogether is smaller than the modulus p. + * + * Since the full `x < p` check is expensive, by default we only prove a weaker assertion, `x < 2^ceil(log2(p))`, + * see {@link ForeignField.assertAlmostReduced} for more details. + * + * This weaker assumption is what we call "almost reduced", and it is represented by the {@link AlmostForeignField} class. + * Note that only {@link AlmostForeignField} supports multiplication and inversion, while {@link UnreducedForeignField} + * only supports addition and subtraction. + * + * This function returns the `Unreduced` class, which will cause the minimum amount of range checks to be created by default. + * If you want to do multiplication, you have two options: + * - create your field elements using the {@link ForeignField.AlmostReduced} constructor, or using the `.provable` type on that class. + * ```ts + * let x = Provable.witness(ForeignField.AlmostReduced.provable, () => ForeignField.from(5)); + * ``` + * - create your field elements normally and convert them using `x.assertAlmostReduced()`. + * ```ts + * let xChecked = x.assertAlmostReduced(); // asserts x < 2^ceil(log2(p)); returns `AlmostForeignField` + * ``` + * + * Similarly, there is a separate class {@link CanonicalForeignField} which represents fully reduced, "canonical" field elements. + * To convert to a canonical field element, use {@link ForeignField.assertCanonical}: + * + * ```ts + * x.assertCanonical(); // asserts x < p; returns `CanonicalForeignField` + * ``` + * You will likely not need canonical fields most of the time. + * + * Base types for all of these classes are separately exported as {@link UnreducedForeignField}, {@link AlmostForeignField} and {@link CanonicalForeignField}., + * + * @param modulus the modulus of the finite field you are instantiating + */ +function createForeignField(modulus: bigint): typeof UnreducedForeignField { + assert( + modulus > 0n, + `ForeignField: modulus must be positive, got ${modulus}` + ); + assert( + modulus < foreignFieldMax, + `ForeignField: modulus exceeds the max supported size of 2^${foreignFieldMaxBits}` + ); + + let Bigint = createField(modulus); + + class UnreducedField extends UnreducedForeignField { + static _Bigint = Bigint; + static _modulus = modulus; + static _provable = provable(UnreducedField); + + // bind public static methods to the class so that they have `this` defined + static from = ForeignField.from.bind(UnreducedField); + static sum = ForeignField.sum.bind(UnreducedField); + static fromBits = ForeignField.fromBits.bind(UnreducedField); + } + + class AlmostField extends AlmostForeignField { + static _Bigint = Bigint; + static _modulus = modulus; + static _provable = provable(AlmostField); + + // bind public static methods to the class so that they have `this` defined + static from = ForeignField.from.bind(AlmostField); + static sum = ForeignField.sum.bind(AlmostField); + static fromBits = ForeignField.fromBits.bind(AlmostField); + static unsafeFrom = AlmostForeignField.unsafeFrom.bind(AlmostField); + } + + class CanonicalField extends CanonicalForeignField { + static _Bigint = Bigint; + static _modulus = modulus; + static _provable = provable(CanonicalField); + + // bind public static methods to the class so that they have `this` defined + static from = ForeignField.from.bind(CanonicalField); + static sum = ForeignField.sum.bind(CanonicalField); + static fromBits = ForeignField.fromBits.bind(CanonicalField); + static unsafeFrom = CanonicalForeignField.unsafeFrom.bind(CanonicalField); + } + + let variants = { + unreduced: UnreducedField, + almostReduced: AlmostField, + canonical: CanonicalField, + }; + UnreducedField._variants = variants; + AlmostField._variants = variants; + CanonicalField._variants = variants; + + return UnreducedField; +} + +// the max foreign field modulus is f_max = floor(sqrt(p * 2^t)), where t = 3*limbBits = 264 and p is the native modulus +// see RFC: https://github.com/o1-labs/proof-systems/blob/1fdb1fd1d112f9d4ee095dbb31f008deeb8150b0/book/src/rfcs/foreign_field_mul.md +// since p = 2^254 + eps for both Pasta fields with eps small, a fairly tight lower bound is +// f_max >= sqrt(2^254 * 2^264) = 2^259 +const foreignFieldMaxBits = (BigInt(Fp.sizeInBits - 1) + 3n * l) / 2n; +const foreignFieldMax = 1n << foreignFieldMaxBits; + +// provable + +type Constructor = new (...args: any[]) => T; + +function provable( + Class: Constructor & { check(x: ForeignField): void } +): ProvablePureExtended { + return { + toFields(x) { + return x.value; + }, + toAuxiliary(): [] { + return []; + }, + sizeInFields() { + return 3; + }, + fromFields(fields) { + let limbs = TupleN.fromArray(3, fields); + return new Class(limbs); + }, + check(x: ForeignField) { + Class.check(x); + }, + // ugh + toJSON(x: ForeignField) { + return x.toBigInt().toString(); + }, + fromJSON(x: string) { + // TODO be more strict about allowed values + return new Class(x); + }, + empty() { + return new Class(0n); + }, + toInput(x) { + let l_ = Number(l); + return { + packed: [ + [x.value[0], l_], + [x.value[1], l_], + [x.value[2], l_], + ], + }; + }, + }; +} diff --git a/src/lib/provable/gadgets/arithmetic.ts b/src/lib/provable/gadgets/arithmetic.ts new file mode 100644 index 0000000000..c5d1bc75f3 --- /dev/null +++ b/src/lib/provable/gadgets/arithmetic.ts @@ -0,0 +1,52 @@ +import { provableTuple } from '../types/struct.js'; +import { Field } from '../wrapped.js'; +import { assert } from '../../util/errors.js'; +import { Provable } from '../provable.js'; +import { rangeCheck32, rangeCheckN } from './range-check.js'; + +export { divMod32, addMod32 }; + +function divMod32(n: Field, quotientBits = 32) { + if (n.isConstant()) { + assert( + n.toBigInt() < 1n << 64n, + `n needs to fit into 64 bit, but got ${n.toBigInt()}` + ); + + let nBigInt = n.toBigInt(); + let q = nBigInt >> 32n; + let r = nBigInt - (q << 32n); + return { + remainder: new Field(r), + quotient: new Field(q), + }; + } + + let [quotient, remainder] = Provable.witness( + provableTuple([Field, Field]), + () => { + let nBigInt = n.toBigInt(); + let q = nBigInt >> 32n; + let r = nBigInt - (q << 32n); + return [new Field(q), new Field(r)]; + } + ); + + if (quotientBits === 1) { + quotient.assertBool(); + } else { + rangeCheckN(quotientBits, quotient); + } + rangeCheck32(remainder); + + n.assertEquals(quotient.mul(1n << 32n).add(remainder)); + + return { + remainder, + quotient, + }; +} + +function addMod32(x: Field, y: Field) { + return divMod32(x.add(y), 1).remainder; +} diff --git a/src/lib/provable/gadgets/basic.ts b/src/lib/provable/gadgets/basic.ts new file mode 100644 index 0000000000..4e811adf3d --- /dev/null +++ b/src/lib/provable/gadgets/basic.ts @@ -0,0 +1,335 @@ +/** + * Basic gadgets that only use generic gates + */ +import { Fp } from '../../../bindings/crypto/finite-field.js'; +import type { Field, VarField } from '../field.js'; +import { + FieldType, + FieldVar, + FieldConst, + VarFieldVar, +} from '../core/fieldvar.js'; +import { toVar } from './common.js'; +import { Gates, fieldVar } from '../gates.js'; +import { TupleN } from '../../util/types.js'; +import { existsOne } from '../core/exists.js'; +import { createField } from '../core/field-constructor.js'; + +export { assertMul, arrayGet, assertOneOf }; + +// internal +export { + reduceToScaledVar, + toLinearCombination, + emptyCell, + linear, + bilinear, + ScaledVar, + Constant, +}; + +/** + * Assert multiplication constraint, `x * y === z` + */ +function assertMul( + x: Field | FieldVar, + y: Field | FieldVar, + z: Field | FieldVar +) { + // simpler version of assertMulCompatible that currently uses the same amount of constraints but is not compatible + // also, doesn't handle all-constant case (handled by calling gadgets already) + + // TODO: if we replace `reduceToScaledVar()` with a function that leaves `a*x + b` unreduced, we can save constraints here + // for example: (x - 1)*(x - 2) === 0 would become 1 constraint instead of 3 + let [[sx, vx], cx] = getLinear(reduceToScaledVar(x)); + let [[sy, vy], cy] = getLinear(reduceToScaledVar(y)); + let [[sz, vz], cz] = getLinear(reduceToScaledVar(z)); + + // (sx * vx + cx) * (sy * vy + cy) = (sz * vz + cz) + // sx*cy*vx + sy*cx*vy - sz*vz + sx*sy*x*vy + (cx*cy - cz) = 0 + + Gates.generic( + { + left: sx * cy, + right: sy * cx, + out: -sz, + mul: sx * sy, + const: cx * cy - cz, + }, + { left: vx, right: vy, out: vz } + ); +} + +// TODO: create constant versions of these and expose on Gadgets + +/** + * Get value from array in O(n) rows. + * + * Assumes that index is in [0, n), returns an unconstrained result otherwise. + * + * Note: This saves 0.5*n constraints compared to equals() + switch() + */ +function arrayGet(array: Field[], index: Field) { + let i = toVar(index); + + // witness result + let a = existsOne(() => array[Number(i.toBigInt())].toBigInt()); + + // we prove a === array[j] + z[j]*(i - j) for some z[j], for all j. + // setting j = i, this implies a === array[i] + // thanks to our assumption that the index i is within bounds, we know that j = i for some j + let n = array.length; + for (let j = 0; j < n; j++) { + let zj = existsOne(() => { + let zj = Fp.div( + Fp.sub(a.toBigInt(), array[j].toBigInt()), + Fp.sub(i.toBigInt(), Fp.fromNumber(j)) + ); + return zj ?? 0n; + }); + // prove that z[j]*(i - j) === a - array[j] + // TODO abstract this logic into a general-purpose assertMul() gadget, + // which is able to use the constant coefficient + // (snarky's assert_r1cs somehow leads to much more constraints than this) + if (array[j].isConstant()) { + // zj*i + (-j)*zj + 0*i + array[j] === a + assertBilinear(zj, i, [1n, -BigInt(j), 0n, array[j].toBigInt()], a); + } else { + let aMinusAj = toVar(a.sub(array[j])); + // zj*i + (-j)*zj + 0*i + 0 === (a - array[j]) + assertBilinear(zj, i, [1n, -BigInt(j), 0n, 0n], aMinusAj); + } + } + + return a; +} + +/** + * Assert that a value equals one of a finite list of constants: + * `(x - c1)*(x - c2)*...*(x - cn) === 0` + * + * TODO: what prevents us from getting the same efficiency with snarky DSL code? + */ +function assertOneOf(x: Field, allowed: [bigint, bigint, ...bigint[]]) { + let xv = toVar(x); + let [c1, c2, ...c] = allowed; + let n = c.length; + if (n === 0) { + // (x - c1)*(x - c2) === 0 + assertBilinear(xv, xv, [1n, -(c1 + c2), 0n, c1 * c2]); + return; + } + // z = (x - c1)*(x - c2) + let z = bilinear(xv, xv, [1n, -(c1 + c2), 0n, c1 * c2]); + + for (let i = 0; i < n; i++) { + if (i < n - 1) { + // z = z*(x - c) + z = bilinear(z, xv, [1n, -c[i], 0n, 0n]); + } else { + // z*(x - c) === 0 + assertBilinear(z, xv, [1n, -c[i], 0n, 0n]); + } + } +} + +// low-level helpers to create generic gates + +/** + * Compute linear function of x: + * `z = a*x + b` + */ +function linear(x: VarField | VarFieldVar, [a, b]: TupleN) { + let z = existsOne(() => { + let x0 = createField(x).toBigInt(); + return a * x0 + b; + }); + // a*x - z + b === 0 + Gates.generic( + { left: a, right: 0n, out: -1n, mul: 0n, const: b }, + { left: x, right: emptyCell(), out: z } + ); + return z; +} + +/** + * Compute bilinear function of x and y: + * `z = a*x*y + b*x + c*y + d` + */ +function bilinear( + x: VarField | VarFieldVar, + y: VarField | VarFieldVar, + [a, b, c, d]: TupleN +) { + let z = existsOne(() => { + let x0 = createField(x).toBigInt(); + let y0 = createField(y).toBigInt(); + return a * x0 * y0 + b * x0 + c * y0 + d; + }); + // b*x + c*y - z + a*x*y + d === 0 + Gates.generic( + { left: b, right: c, out: -1n, mul: a, const: d }, + { left: x, right: y, out: z } + ); + return z; +} + +/** + * Assert bilinear equation on x, y and z: + * `a*x*y + b*x + c*y + d === z` + * + * The default for z is 0. + */ +function assertBilinear( + x: VarField | VarFieldVar, + y: VarField | VarFieldVar, + [a, b, c, d]: TupleN, + z?: VarField | VarFieldVar +) { + // b*x + c*y - z? + a*x*y + d === 0 + Gates.generic( + { left: b, right: c, out: z === undefined ? 0n : -1n, mul: a, const: d }, + { left: x, right: y, out: z === undefined ? emptyCell() : z } + ); +} + +function emptyCell() { + return existsOne(() => 0n); +} + +/** + * Converts a `FieldVar` into a set of constraints, returns the remainder as a ScaledVar | Constant + * + * Collapses duplicated variables, so e.g. x - x just becomes the 0 constant. + * + * This is better than fully reducing to a Var, because it allows callers to fold the scaling factor into the next operation, + * instead of wasting a constraint on `c * x === y` before using `c * x`. + */ +function reduceToScaledVar(x: Field | FieldVar): ScaledVar | Constant { + let { constant: c, terms } = toLinearCombination(fieldVar(x)); + + // sort terms alphabetically by variable index + // (to match snarky implementation) + terms.sort(([, [, i]], [, [, j]]) => i - j); + + if (terms.length === 0) { + // constant + return [FieldType.Constant, FieldConst.fromBigint(c)]; + } + + if (terms.length === 1) { + let [s, x] = terms[0]; + if (c === 0n) { + // s*x + return [FieldType.Scale, FieldConst.fromBigint(s), x]; + } else { + // res = s*x + c + let res = linear(x, [s, c]); + return [FieldType.Scale, FieldConst[1], res.value]; + } + } + + // res = s0*x0 + s1*x1 + ... + sn*xn + c + let [[s0, x0], ...rest] = terms; + + let [s1, x1] = rest.pop()!; + + for (let [si, xi] of rest.reverse()) { + // x1 = s1*x1 + si*xi + x1 = bilinear(xi, x1, [0n, si, s1, 0n]).value; + s1 = 1n; + } + + // res = s0*x0 + s1*x1 + c + let res = bilinear(x0, x1, [0n, s0, s1, c]); + return [FieldType.Scale, FieldConst[1], res.value]; +} + +/** + * Flatten the AST of a `FieldVar` to a linear combination of the form + * + * `c + s1*x1 + s2*x2 + ... + sn*xn` + * + * where none of the vars xi are duplicated. + */ +function toLinearCombination( + x: FieldVar, + sx = 1n, + lincom: { constant: bigint; terms: [bigint, VarFieldVar][] } = { + constant: 0n, + terms: [], + } +): { constant: bigint; terms: [bigint, VarFieldVar][] } { + let { constant, terms } = lincom; + // the recursive logic here adds a new term sx*x to an existing linear combination + // but x itself is an AST + + if (sx === 0n) return lincom; + + switch (x[0]) { + case FieldType.Constant: { + // a constant is added to the constant term + let [, [, c]] = x; + return { constant: Fp.add(constant, Fp.mul(sx, c)), terms }; + } + case FieldType.Var: { + // a variable is added to the terms or included in an existing one + let [, i] = x; + + // we search for an existing term with the same var + let y = terms.find((t) => t[1][1] === i); + + // if there is none, we just add a new term + if (y === undefined) return { constant, terms: [[sx, x], ...terms] }; + + // if there is one, we add the scales + let [sy] = y; + y[0] = Fp.add(sy, sx); + + if (y[0] === 0n) { + // if the sum is 0, we remove the term + terms = terms.filter((t) => t[1][1] !== i); + } + + return { constant, terms }; + } + case FieldType.Scale: { + // sx * (s * x) + ... = (sx * s) * x + ... + let [, [, s], v] = x; + return toLinearCombination(v, Fp.mul(sx, s), lincom); + } + case FieldType.Add: { + // sx * (x1 + x2) + ... = sx * x2 + (sx * x1 + ...) + let [, x1, x2] = x; + lincom = toLinearCombination(x1, sx, lincom); + return toLinearCombination(x2, sx, lincom); + } + } +} + +// helpers for dealing with scaled vars and constants + +// type Scaled = [FieldType.Scale, FieldConst, FieldVar]; +type ScaledVar = [FieldType.Scale, FieldConst, VarFieldVar]; +type Constant = [FieldType.Constant, FieldConst]; + +function isVar(x: ScaledVar | Constant): x is ScaledVar { + return x[0] === FieldType.Scale; +} +function isConst(x: ScaledVar | Constant): x is Constant { + return x[0] === FieldType.Constant; +} + +function getVar(x: ScaledVar): [bigint, VarFieldVar] { + return [x[1][1], x[2]]; +} +function getConst(x: Constant): bigint { + return x[1][1]; +} + +function getLinear(x: ScaledVar | Constant): [[bigint, VarFieldVar], bigint] { + if (isVar(x)) return [getVar(x), 0n]; + return [[0n, emptyCell().value], getConst(x)]; +} + +const ScaledVar = { isVar, getVar, isConst, getConst }; diff --git a/src/lib/provable/gadgets/bit-slices.ts b/src/lib/provable/gadgets/bit-slices.ts new file mode 100644 index 0000000000..68d132b51a --- /dev/null +++ b/src/lib/provable/gadgets/bit-slices.ts @@ -0,0 +1,156 @@ +/** + * Gadgets for converting between field elements and bit slices of various lengths + */ +import { bigIntToBits } from '../../../bindings/crypto/bigint-helpers.js'; +import { Field } from '../field.js'; +import { UInt8 } from '../int.js'; +import { exists } from '../core/exists.js'; +import { Provable } from '../provable.js'; +import { chunk } from '../../util/arrays.js'; +import { assert } from './common.js'; +import type { Field3 } from './foreign-field.js'; +import { l } from './range-check.js'; + +export { bytesToWord, wordToBytes, wordsToBytes, bytesToWords, sliceField3 }; + +// conversion between bytes and multi-byte words + +/** + * Convert an array of UInt8 to a Field element. Expects little endian representation. + */ +function bytesToWord(wordBytes: UInt8[]): Field { + return wordBytes.reduce((acc, byte, idx) => { + const shift = 1n << BigInt(8 * idx); + return acc.add(byte.value.mul(shift)); + }, Field.from(0)); +} + +/** + * Convert a Field element to an array of UInt8. Expects little endian representation. + * @param bytesPerWord number of bytes per word + */ +function wordToBytes(word: Field, bytesPerWord = 8): UInt8[] { + let bytes = Provable.witness(Provable.Array(UInt8, bytesPerWord), () => { + let w = word.toBigInt(); + return Array.from({ length: bytesPerWord }, (_, k) => + UInt8.from((w >> BigInt(8 * k)) & 0xffn) + ); + }); + + // check decomposition + bytesToWord(bytes).assertEquals(word); + + return bytes; +} + +/** + * Convert an array of Field elements to an array of UInt8. Expects little endian representation. + * @param bytesPerWord number of bytes per word + */ +function wordsToBytes(words: Field[], bytesPerWord = 8): UInt8[] { + return words.flatMap((w) => wordToBytes(w, bytesPerWord)); +} +/** + * Convert an array of UInt8 to an array of Field elements. Expects little endian representation. + * @param bytesPerWord number of bytes per word + */ +function bytesToWords(bytes: UInt8[], bytesPerWord = 8): Field[] { + return chunk(bytes, bytesPerWord).map(bytesToWord); +} + +// conversion between 3-limb foreign fields and arbitrary bit slices + +/** + * Provable method for slicing a 3x88-bit bigint into smaller bit chunks of length `chunkSize` + * + * This serves as a range check that the input is in [0, 2^maxBits) + */ +function sliceField3( + [x0, x1, x2]: Field3, + { maxBits, chunkSize }: { maxBits: number; chunkSize: number } +) { + let l_ = Number(l); + assert(maxBits <= 3 * l_, `expected max bits <= 3*${l_}, got ${maxBits}`); + + // first limb + let result0 = sliceField(x0, Math.min(l_, maxBits), chunkSize); + if (maxBits <= l_) return result0.chunks; + maxBits -= l_; + + // second limb + let result1 = sliceField(x1, Math.min(l_, maxBits), chunkSize, result0); + if (maxBits <= l_) return result0.chunks.concat(result1.chunks); + maxBits -= l_; + + // third limb + let result2 = sliceField(x2, maxBits, chunkSize, result1); + return result0.chunks.concat(result1.chunks, result2.chunks); +} + +/** + * Provable method for slicing a field element into smaller bit chunks of length `chunkSize`. + * + * This serves as a range check that the input is in [0, 2^maxBits) + * + * If `chunkSize` does not divide `maxBits`, the last chunk will be smaller. + * We return the number of free bits in the last chunk, and optionally accept such a result from a previous call, + * so that this function can be used to slice up a bigint of multiple limbs into homogeneous chunks. + * + * TODO: atm this uses expensive boolean checks for each bit. + * For larger chunks, we should use more efficient range checks. + */ +function sliceField( + x: Field, + maxBits: number, + chunkSize: number, + leftover?: { chunks: Field[]; leftoverSize: number } +) { + let bits = exists(maxBits, () => { + let bits = bigIntToBits(x.toBigInt()); + // normalize length + if (bits.length > maxBits) bits = bits.slice(0, maxBits); + if (bits.length < maxBits) + bits = bits.concat(Array(maxBits - bits.length).fill(false)); + return bits.map(BigInt); + }); + + let chunks = []; + let sum = Field.from(0n); + + // if there's a leftover chunk from a previous sliceField() call, we complete it + if (leftover !== undefined) { + let { chunks: previous, leftoverSize: size } = leftover; + let remainingChunk = Field.from(0n); + for (let i = 0; i < size; i++) { + let bit = bits[i]; + bit.assertBool(); + remainingChunk = remainingChunk.add(bit.mul(1n << BigInt(i))); + } + sum = remainingChunk = remainingChunk.seal(); + let chunk = previous[previous.length - 1]; + previous[previous.length - 1] = chunk.add( + remainingChunk.mul(1n << BigInt(chunkSize - size)) + ); + } + + let i = leftover?.leftoverSize ?? 0; + for (; i < maxBits; i += chunkSize) { + // prove that chunk has `chunkSize` bits + // TODO: this inner sum should be replaced with a more efficient range check when possible + let chunk = Field.from(0n); + let size = Math.min(maxBits - i, chunkSize); // last chunk might be smaller + for (let j = 0; j < size; j++) { + let bit = bits[i + j]; + bit.assertBool(); + chunk = chunk.add(bit.mul(1n << BigInt(j))); + } + chunk = chunk.seal(); + // prove that chunks add up to x + sum = sum.add(chunk.mul(1n << BigInt(i))); + chunks.push(chunk); + } + sum.assertEquals(x); + + let leftoverSize = i - maxBits; + return { chunks, leftoverSize } as const; +} diff --git a/src/lib/provable/gadgets/bitwise.ts b/src/lib/provable/gadgets/bitwise.ts new file mode 100644 index 0000000000..b711f8dd16 --- /dev/null +++ b/src/lib/provable/gadgets/bitwise.ts @@ -0,0 +1,351 @@ +import { Provable } from '../provable.js'; +import { Fp } from '../../../bindings/crypto/finite-field.js'; +import { Field } from '../field.js'; +import { Gates } from '../gates.js'; +import { assert, divideWithRemainder, toVar, bitSlice } from './common.js'; +import { rangeCheck32, rangeCheck64 } from './range-check.js'; +import { divMod32 } from './arithmetic.js'; +import { exists } from '../../provable/core/exists.js'; + +export { + xor, + not, + rotate64, + rotate32, + and, + rightShift64, + leftShift64, + leftShift32, +}; + +function not(a: Field, length: number, checked: boolean = false) { + // check that input length is positive + assert(length > 0, `Input length needs to be positive values.`); + + // Check that length does not exceed maximum field size in bits + assert( + length < Field.sizeInBits, + `Length ${length} exceeds maximum of ${Field.sizeInBits} bits.` + ); + + // obtain pad length until the length is a multiple of 16 for n-bit length lookup table + let padLength = Math.ceil(length / 16) * 16; + + // handle constant case + if (a.isConstant()) { + let max = 1n << BigInt(padLength); + assert( + a.toBigInt() < max, + `${a.toBigInt()} does not fit into ${padLength} bits` + ); + return new Field(Fp.not(a.toBigInt(), length)); + } + + // create a bitmask with all ones + let allOnes = new Field(2n ** BigInt(length) - 1n); + + if (checked) { + return xor(a, allOnes, length); + } else { + return allOnes.sub(a).seal(); + } +} + +function xor(a: Field, b: Field, length: number) { + // check that both input lengths are positive + assert(length > 0, `Input lengths need to be positive values.`); + + // check that length does not exceed maximum 254 size in bits + assert(length <= 254, `Length ${length} exceeds maximum of 254 bits.`); + + // obtain pad length until the length is a multiple of 16 for n-bit length lookup table + let padLength = Math.ceil(length / 16) * 16; + + // handle constant case + if (a.isConstant() && b.isConstant()) { + let max = 1n << BigInt(padLength); + + assert(a.toBigInt() < max, `${a} does not fit into ${padLength} bits`); + assert(b.toBigInt() < max, `${b} does not fit into ${padLength} bits`); + + return new Field(a.toBigInt() ^ b.toBigInt()); + } + + // calculate expected xor output + let outputXor = Provable.witness( + Field, + () => new Field(a.toBigInt() ^ b.toBigInt()) + ); + + // builds the xor gadget chain + buildXor(a, b, outputXor, padLength); + + // return the result of the xor operation + return outputXor; +} + +// builds a xor chain +function buildXor(a: Field, b: Field, out: Field, padLength: number) { + // construct the chain of XORs until padLength is 0 + while (padLength !== 0) { + // slices the inputs into 4x 4bit-sized chunks + let slices = exists(15, () => { + let a0 = a.toBigInt(); + let b0 = b.toBigInt(); + let out0 = out.toBigInt(); + return [ + // slices of a + bitSlice(a0, 0, 4), + bitSlice(a0, 4, 4), + bitSlice(a0, 8, 4), + bitSlice(a0, 12, 4), + + // slices of b + bitSlice(b0, 0, 4), + bitSlice(b0, 4, 4), + bitSlice(b0, 8, 4), + bitSlice(b0, 12, 4), + + // slices of expected output + bitSlice(out0, 0, 4), + bitSlice(out0, 4, 4), + bitSlice(out0, 8, 4), + bitSlice(out0, 12, 4), + + // next values + a0 >> 16n, + b0 >> 16n, + out0 >> 16n, + ]; + }); + + // prettier-ignore + let [ + in1_0, in1_1, in1_2, in1_3, + in2_0, in2_1, in2_2, in2_3, + out0, out1, out2, out3, + aNext, bNext, outNext + ] = slices; + + // assert that the xor of the slices is correct, 16 bit at a time + // prettier-ignore + Gates.xor( + a, b, out, + in1_0, in1_1, in1_2, in1_3, + in2_0, in2_1, in2_2, in2_3, + out0, out1, out2, out3 + ); + + // update the values for the next loop iteration + a = aNext; + b = bNext; + out = outNext; + padLength = padLength - 16; + } + + // inputs are zero and length is zero, add the zero check - we reached the end of our chain + Gates.zero(a, b, out); + + let zero = new Field(0); + zero.assertEquals(a); + zero.assertEquals(b); + zero.assertEquals(out); +} + +function and(a: Field, b: Field, length: number) { + // check that both input lengths are positive + assert(length > 0, `Input lengths need to be positive values.`); + + // check that length does not exceed maximum field size in bits + assert( + length <= Field.sizeInBits, + `Length ${length} exceeds maximum of ${Field.sizeInBits} bits.` + ); + + // obtain pad length until the length is a multiple of 16 for n-bit length lookup table + let padLength = Math.ceil(length / 16) * 16; + + // handle constant case + if (a.isConstant() && b.isConstant()) { + let max = 1n << BigInt(padLength); + + assert(a.toBigInt() < max, `${a} does not fit into ${padLength} bits`); + assert(b.toBigInt() < max, `${b} does not fit into ${padLength} bits`); + + return new Field(a.toBigInt() & b.toBigInt()); + } + + // calculate expect and output + let outputAnd = Provable.witness( + Field, + () => new Field(a.toBigInt() & b.toBigInt()) + ); + + // compute values for gate + // explanation: https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and + let sum = a.add(b); + let xorOutput = xor(a, b, length); + outputAnd.mul(2).add(xorOutput).assertEquals(sum); + + // return the result of the and operation + return outputAnd; +} + +function rotate64( + field: Field, + bits: number, + direction: 'left' | 'right' = 'left' +) { + // Check that the rotation bits are in range + assert( + bits >= 0 && bits <= 64, + `rotation: expected bits to be between 0 and 64, got ${bits}` + ); + + if (field.isConstant()) { + assert( + field.toBigInt() < 1n << 64n, + `rotation: expected field to be at most 64 bits, got ${field.toBigInt()}` + ); + return new Field(Fp.rot(field.toBigInt(), BigInt(bits), direction)); + } + const [rotated] = rot64(field, bits, direction); + return rotated; +} + +function rotate32( + field: Field, + bits: number, + direction: 'left' | 'right' = 'left' +) { + assert(bits <= 32 && bits > 0, 'bits must be between 0 and 32'); + + if (field.isConstant()) { + assert( + field.toBigInt() < 1n << 32n, + `rotation: expected field to be at most 32 bits, got ${field.toBigInt()}` + ); + return new Field(Fp.rot(field.toBigInt(), BigInt(bits), direction, 32n)); + } + + let { quotient: excess, remainder: shifted } = divMod32( + field.mul(1n << BigInt(direction === 'left' ? bits : 32 - bits)) + ); + + let rotated = shifted.add(excess); + + rangeCheck32(rotated); + + return rotated; +} + +function rot64( + field: Field, + bits: number, + direction: 'left' | 'right' = 'left' +): [Field, Field, Field] { + const rotationBits = direction === 'right' ? 64 - bits : bits; + const big2Power64 = 1n << 64n; + const big2PowerRot = 1n << BigInt(rotationBits); + + const [rotated, excess, shifted, bound] = Provable.witness( + Provable.Array(Field, 4), + () => { + const f = field.toBigInt(); + + // Obtain rotated output, excess, and shifted for the equation: + // f * 2^rot = excess * 2^64 + shifted + const { quotient: excess, remainder: shifted } = divideWithRemainder( + f * big2PowerRot, + big2Power64 + ); + + // Compute rotated value as: rotated = excess + shifted + const rotated = shifted + excess; + // Compute bound to check excess < 2^rot + const bound = excess + big2Power64 - big2PowerRot; + return [rotated, excess, shifted, bound].map(Field.from); + } + ); + + // flush zero var to prevent broken gate chain (zero is used in rangeCheck64) + // TODO this is an abstraction leak, but not clear to me how to improve + toVar(0n); + + // slice the bound into chunks + let boundSlices = exists(12, () => { + let bound0 = bound.toBigInt(); + return [ + bitSlice(bound0, 52, 12), // bits 52-64 + bitSlice(bound0, 40, 12), // bits 40-52 + bitSlice(bound0, 28, 12), // bits 28-40 + bitSlice(bound0, 16, 12), // bits 16-28 + + bitSlice(bound0, 14, 2), // bits 14-16 + bitSlice(bound0, 12, 2), // bits 12-14 + bitSlice(bound0, 10, 2), // bits 10-12 + bitSlice(bound0, 8, 2), // bits 8-10 + bitSlice(bound0, 6, 2), // bits 6-8 + bitSlice(bound0, 4, 2), // bits 4-6 + bitSlice(bound0, 2, 2), // bits 2-4 + bitSlice(bound0, 0, 2), // bits 0-2 + ]; + }); + let [b52, b40, b28, b16, b14, b12, b10, b8, b6, b4, b2, b0] = boundSlices; + + // Compute current row + Gates.rotate( + field, + rotated, + excess, + [b52, b40, b28, b16], + [b14, b12, b10, b8, b6, b4, b2, b0], + big2PowerRot + ); + // Compute next row + rangeCheck64(shifted); + // note: range-checking `shifted` and `field` is enough. + // * excess < 2^rot follows from the bound check and the rotation equation in the gate + // * rotated < 2^64 follows from rotated = excess + shifted (because shifted has to be a multiple of 2^rot) + // for a proof, see https://github.com/o1-labs/o1js/pull/1201 + return [rotated, excess, shifted]; +} + +function rightShift64(field: Field, bits: number) { + assert( + bits >= 0 && bits <= 64, + `rightShift: expected bits to be between 0 and 64, got ${bits}` + ); + + if (field.isConstant()) { + assert( + field.toBigInt() < 1n << 64n, + `rightShift: expected field to be at most 64 bits, got ${field.toBigInt()}` + ); + return new Field(Fp.rightShift(field.toBigInt(), bits)); + } + const [, excess] = rot64(field, bits, 'right'); + return excess; +} + +function leftShift64(field: Field, bits: number) { + assert( + bits >= 0 && bits <= 64, + `rightShift: expected bits to be between 0 and 64, got ${bits}` + ); + + if (field.isConstant()) { + assert( + field.toBigInt() < 1n << 64n, + `rightShift: expected field to be at most 64 bits, got ${field.toBigInt()}` + ); + return new Field(Fp.leftShift(field.toBigInt(), bits)); + } + const [, , shifted] = rot64(field, bits, 'left'); + return shifted; +} + +function leftShift32(field: Field, bits: number) { + let { remainder: shifted } = divMod32(field.mul(1n << BigInt(bits))); + return shifted; +} diff --git a/src/lib/provable/gadgets/common.ts b/src/lib/provable/gadgets/common.ts new file mode 100644 index 0000000000..a1a065f2fd --- /dev/null +++ b/src/lib/provable/gadgets/common.ts @@ -0,0 +1,63 @@ +import type { Field, VarField } from '../field.js'; +import { FieldVar, VarFieldVar } from '../core/fieldvar.js'; +import { Tuple } from '../../util/types.js'; +import type { Bool } from '../bool.js'; +import { fieldVar } from '../gates.js'; +import { existsOne } from '../core/exists.js'; +import { createField } from '../core/field-constructor.js'; + +export { toVars, toVar, isVar, assert, bitSlice, divideWithRemainder }; + +/** + * Given a Field, collapse its AST to a pure Var. See {@link FieldVar}. + * + * This is useful to prevent rogue Generic gates added in the middle of gate chains, + * which are caused by snarky auto-resolving constants, adds and scales to vars. + * + * Same as `Field.seal()` with the difference that `seal()` leaves constants as is. + */ +function toVar(x_: Field | FieldVar | bigint): VarField { + let x = createField(x_); + // don't change existing vars + if (isVar(x)) return x; + let xVar = existsOne(() => x.toBigInt()); + xVar.assertEquals(x); + return xVar; +} + +function isVar(x: FieldVar | bigint): x is VarFieldVar; +function isVar(x: Field | bigint): x is VarField; +function isVar(x: Field | FieldVar | bigint) { + return FieldVar.isVar(fieldVar(x)); +} + +/** + * Apply {@link toVar} to each element of a tuple. + */ +function toVars>( + fields: T +): { [k in keyof T]: VarField } { + return Tuple.map(fields, toVar); +} + +/** + * Assert that a statement is true. If the statement is false, throws an error with the given message. + * Can be used in provable code. + */ +function assert(stmt: boolean | Bool, message?: string): asserts stmt { + if (typeof stmt === 'boolean') { + if (!stmt) throw Error(message ?? 'Assertion failed'); + } else { + stmt.assertTrue(message ?? 'Assertion failed'); + } +} + +function bitSlice(x: bigint, start: number, length: number) { + return (x >> BigInt(start)) & ((1n << BigInt(length)) - 1n); +} + +function divideWithRemainder(numerator: bigint, denominator: bigint) { + const quotient = numerator / denominator; + const remainder = numerator - denominator * quotient; + return { quotient, remainder }; +} diff --git a/src/lib/provable/gadgets/comparison.ts b/src/lib/provable/gadgets/comparison.ts new file mode 100644 index 0000000000..15ae06061c --- /dev/null +++ b/src/lib/provable/gadgets/comparison.ts @@ -0,0 +1,271 @@ +import type { Field } from '../field.js'; +import type { Bool } from '../bool.js'; +import { createBoolUnsafe, createField } from '../core/field-constructor.js'; +import { Fp } from '../../../bindings/crypto/finite-field.js'; +import { assert } from '../../../lib/util/assert.js'; +import { exists, existsOne } from '../core/exists.js'; +import { assertMul } from './compatible.js'; +import { Field3, ForeignField } from './foreign-field.js'; +import { l, l2, multiRangeCheck } from './range-check.js'; +import { witness } from '../types/witness.js'; + +export { + // generic comparison gadgets for inputs in a narrower range < p/2 + assertLessThanGeneric, + assertLessThanOrEqualGeneric, + lessThanGeneric, + lessThanOrEqualGeneric, + // comparison gadgets for full range inputs + assertLessThanFull, + assertLessThanOrEqualFull, + lessThanFull, + lessThanOrEqualFull, + // legacy, unused + compareCompatible, +}; + +/** + * Prove x <= y assuming 0 <= x, y < c. + * The upper bound c must satisfy 2c <= p, where p is the field order. + * + * Expects a function `rangeCheck(v: Field)` which proves that v is in [0, p-c). + * (Note: the range check on v can be looser than the assumption on x and y, but it doesn't have to be) + * The efficiency of the gadget largely depends on the efficiency of `rangeCheck()`. + * + * **Warning:** The gadget does not prove x <= y if either 2c > p or x or y are not in [0, c). + * Neither of these conditions are enforced by the gadget. + */ +function assertLessThanOrEqualGeneric( + x: Field, + y: Field, + rangeCheck: (v: Field) => void +) { + // since 0 <= x, y < c, we have y - x in [0, c) u (p-c, p) + // because of c <= p-c, the two ranges are disjoint. therefore, + // y - x in [0, p-c) is equivalent to x <= y + rangeCheck(y.sub(x).seal()); +} + +/** + * Prove x < y assuming 0 <= x, y < c. + * + * Assumptions are the same as in {@link assertLessThanOrEqualGeneric}. + */ +function assertLessThanGeneric( + x: Field, + y: Field, + rangeCheck: (v: Field) => void +) { + // since 0 <= x, y < c, we have y - 1 - x in [0, c) u [p-c, p) + // because of c <= p-c, the two ranges are disjoint. therefore, + // y - 1 - x in [0, p-c) is equivalent to x <= y - 1 which is equivalent to x < y + rangeCheck(y.sub(1).sub(x).seal()); +} + +/** + * Return a Bool b that is true if and only if x < y. + * + * Assumptions are similar as in {@link assertLessThanOrEqualGeneric}, with some important differences: + * - c is a required input + * - the `rangeCheck` function must fully prove that its input is in [0, c) + */ +function lessThanGeneric( + x: Field, + y: Field, + c: bigint, + rangeCheck: (v: Field) => void +) { + // we prove that there exists b such that b*c + x - y is in [0, c) + // if b = 0, this implies x - y is in [0, c), and so x >= y + // if b = 1, this implies x - y is in [p-c, p), and so x < y because p-c >= c + let b = existsOne(() => BigInt(x.toBigInt() < y.toBigInt())); + let isLessThan = b.assertBool(); + + // b*c + x - y in [0, c) + rangeCheck(b.mul(c).add(x).sub(y).seal()); + + return isLessThan; +} + +/** + * Return a Bool b that is true if and only if x <= y. + * + * Assumptions are similar as in {@link assertLessThanOrEqualGeneric}, with some important differences: + * - c is a required input + * - the `rangeCheck` function must fully prove that its input is in [0, c) + */ +function lessThanOrEqualGeneric( + x: Field, + y: Field, + c: bigint, + rangeCheck: (v: Field) => void +) { + // we prove that there exists b such that b*c + x - y - 1 is in [0, c) + // if b = 0, this implies x - y - 1 is in [0, c), and so x > y + // if b = 1, this implies x - y - 1 is in [p-c, p), and so x <= y because p-c >= c + let b = existsOne(() => BigInt(x.toBigInt() <= y.toBigInt())); + let isLessThanOrEqual = b.assertBool(); + + // b*c + x - y - 1 in [0, c) + rangeCheck(b.mul(c).add(x).sub(y).sub(1).seal()); + + return isLessThanOrEqual; +} + +/** + * Assert that x < y. + * + * There are no assumptions on the range of x and y, they can occupy the full range [0, p). + */ +function assertLessThanFull(x: Field, y: Field) { + let xBig = fieldToField3(x); + let yBig = fieldToField3(y); + + // x < y as bigints + ForeignField.assertLessThan(xBig, yBig); + + // y < p, so y is canonical. implies x < p as well. + // (if we didn't do this check, we would prove nothing. + // e.g. yBig could be the bigint representation of y + p, and only _therefore_ larger than xBig) + ForeignField.assertLessThan(yBig, Fp.modulus); +} + +/** + * Assert that x <= y. + * + * There are no assumptions on the range of x and y, they can occupy the full range [0, p). + */ +function assertLessThanOrEqualFull(x: Field, y: Field) { + let xBig = fieldToField3(x); + let yBig = fieldToField3(y); + ForeignField.assertLessThanOrEqual(xBig, yBig); + ForeignField.assertLessThan(yBig, Fp.modulus); +} + +/** + * Return a Bool b that is true if and only if x < y. + * + * There are no assumptions on the range of x and y, they can occupy the full range [0, p). + */ +function lessThanFull(x: Field, y: Field) { + // same logic as in lessThanGeneric: + // we witness b such that b*p + x - y is in [0, p), where the sum is done in bigint arithmetic + // if b = 0, x - y is in [0, p), and so x >= y + // if b = 1, x - y is in [-p, 0), and so x < y + // we must also check that both x and y are canonical, or else the connection between the bigint and the Field is lost + let b = existsOne(() => BigInt(x.toBigInt() < y.toBigInt())); + let isLessThan = b.assertBool(); + + let xBig = fieldToField3(x); + let yBig = fieldToField3(y); + ForeignField.assertLessThan(xBig, Fp.modulus); + ForeignField.assertLessThan(yBig, Fp.modulus); + + let [p0, p1, p2] = Field3.from(Fp.modulus); + let bTimesP: Field3 = [p0.mul(b), p1.mul(b), p2.mul(b)]; + + // b*p + x - y in [0, p) + let z = ForeignField.sum([bTimesP, xBig, yBig], [1n, -1n], 0n); + ForeignField.assertLessThan(z, Fp.modulus); + + return isLessThan; +} + +/** + * Return a Bool b that is true if and only if x <= y. + * + * There are no assumptions on the range of x and y, they can occupy the full range [0, p). + */ +function lessThanOrEqualFull(x: Field, y: Field) { + // keep it simple and just use x <= y <=> !(y < x) + return lessThanFull(y, x).not(); +} + +/** + * internal helper, split Field into a 3-limb bigint + * + * **Warning:** the output is underconstrained up to a multiple of the modulus that could be added to the bigint. + */ +function fieldToField3(x: Field) { + if (x.isConstant()) return Field3.from(x.toBigInt()); + + let xBig = witness(Field3.provable, () => Field3.from(x.toBigInt())); + multiRangeCheck(xBig); + let [x0, x1, x2] = xBig; + + // prove that x == x0 + x1*2^l + x2*2^2l + let x_ = x0.add(x1.mul(1n << l)).add(x2.mul(1n << l2)); + x_.assertEquals(x); + return xBig; +} + +/** + * Compare x and y assuming both have at most `n` bits. + * + * **Important:** If `x` and `y` have more than `n` bits, this doesn't prove the comparison correctly. + * It is up to the caller to prove that `x` and `y` have at most `n` bits. + * + * **Warning:** This was created for 1:1 compatibility with snarky's `compare` gadget. + * It was designed for R1CS and is extremely inefficient when used with plonkish arithmetization. + */ +function compareCompatible(x: Field, y: Field, n = Fp.sizeInBits - 2) { + let maxLength = Fp.sizeInBits - 2; + assert(n <= maxLength, `bitLength must be at most ${maxLength}`); + + // z = 2^n + y - x + let z = createField(1n << BigInt(n)) + .add(y) + .sub(x); + + let zBits = unpack(z, n + 1); + + // highest (n-th) bit tells us if z >= 2^n + // which is equivalent to x <= y + let lessOrEqual = zBits[n]; + + // other bits tell us if x = y + let prefix = zBits.slice(0, n); + let notAllZeros = any(prefix); + let less = lessOrEqual.and(notAllZeros); + + return { lessOrEqual, less }; +} + +// helper functions for `compareCompatible()` + +// custom version of toBits to be compatible +function unpack(x: Field, length: number) { + let bits = exists(length, () => { + let x0 = x.toBigInt(); + return Array.from({ length }, (_, k) => (x0 >> BigInt(k)) & 1n); + }); + bits.forEach((b) => b.assertBool()); + let lc = bits.reduce( + (acc, b, i) => acc.add(b.mul(1n << BigInt(i))), + createField(0) + ); + assertMul(lc, createField(1), x); + return bits.map((b) => createBoolUnsafe(b)); +} + +function any(xs: Bool[]) { + let sum = xs.reduce((a, b) => a.add(b.toField()), createField(0)); + let allZero = isZero(sum); + return allZero.not(); +} + +// custom isZero to be compatible +function isZero(x: Field): Bool { + // create witnesses z = 1/x (or z=0 if x=0), and b = 1 - zx + let [b, z] = exists(2, () => { + let xmy = x.toBigInt(); + let z = Fp.inverse(xmy) ?? 0n; + let b = Fp.sub(1n, Fp.mul(z, xmy)); + return [b, z]; + }); + // b * x === 0 + assertMul(b, x, createField(0)); + // z * x === 1 - b + assertMul(z, x, createField(1).sub(b)); + return createBoolUnsafe(b); +} diff --git a/src/lib/provable/gadgets/compatible.ts b/src/lib/provable/gadgets/compatible.ts new file mode 100644 index 0000000000..a10382497e --- /dev/null +++ b/src/lib/provable/gadgets/compatible.ts @@ -0,0 +1,250 @@ +/** + * Basic gadgets that only use generic gates, and are compatible with (create the same constraints as) + * `plonk_constraint_system.ml` / R1CS_constraint_system. + */ +import { Fp } from '../../../bindings/crypto/finite-field.js'; +import { Field } from '../field.js'; +import { FieldVar } from '../core/fieldvar.js'; +import { assert } from './common.js'; +import { Gates } from '../gates.js'; +import { ScaledVar, emptyCell, reduceToScaledVar } from './basic.js'; +import { Snarky } from '../../../snarky.js'; + +export { + assertMulCompatible as assertMul, + assertSquareCompatible as assertSquare, + assertBooleanCompatible as assertBoolean, + assertEqualCompatible as assertEqual, +}; + +let { isVar, getVar, isConst, getConst } = ScaledVar; + +/** + * Assert multiplication constraint, `x * y === z` + */ +function assertMulCompatible( + x: Field | FieldVar, + y: Field | FieldVar, + z: Field | FieldVar +) { + // this faithfully implements snarky's `assert_r1cs`, + // see `R1CS_constraint_system.add_constraint` -> `Snarky_backendless.Constraint.R1CS` + + let xv = reduceToScaledVar(x); + let yv = reduceToScaledVar(y); + let zv = reduceToScaledVar(z); + + // three variables + + if (isVar(xv) && isVar(yv) && isVar(zv)) { + let [[sx, x], [sy, y], [sz, z]] = [getVar(xv), getVar(yv), getVar(zv)]; + + // -sx sy * x y + sz z = 0 + return Gates.generic( + { left: 0n, right: 0n, out: sz, mul: -sx * sy, const: 0n }, + { left: x, right: y, out: z } + ); + } + + // two variables, one constant + + if (isVar(xv) && isVar(yv) && isConst(zv)) { + let [[sx, x], [sy, y], sz] = [getVar(xv), getVar(yv), getConst(zv)]; + + // sx sy * x y - sz = 0 + return Gates.generic( + { left: 0n, right: 0n, out: 0n, mul: sx * sy, const: -sz }, + { left: x, right: y, out: emptyCell() } + ); + } + + if (isVar(xv) && isConst(yv) && isVar(zv)) { + let [[sx, x], sy, [sz, z]] = [getVar(xv), getConst(yv), getVar(zv)]; + + // sx sy * x - sz z = 0 + return Gates.generic( + { left: sx * sy, right: 0n, out: -sz, mul: 0n, const: 0n }, + { left: x, right: emptyCell(), out: z } + ); + } + + if (isConst(xv) && isVar(yv) && isVar(zv)) { + let [sx, [sy, y], [sz, z]] = [getConst(xv), getVar(yv), getVar(zv)]; + + // sx sy * y - sz z = 0 + return Gates.generic( + { left: 0n, right: sx * sy, out: -sz, mul: 0n, const: 0n }, + { left: emptyCell(), right: y, out: z } + ); + } + + // two constants, one variable + + if (isVar(xv) && isConst(yv) && isConst(zv)) { + let [[sx, x], sy, sz] = [getVar(xv), getConst(yv), getConst(zv)]; + + // sx sy * x - sz = 0 + return Gates.generic( + { left: sx * sy, right: 0n, out: 0n, mul: 0n, const: -sz }, + { left: x, right: emptyCell(), out: emptyCell() } + ); + } + + if (isConst(xv) && isVar(yv) && isConst(zv)) { + let [sx, [sy, y], sz] = [getConst(xv), getVar(yv), getConst(zv)]; + + // sx sy * y - sz = 0 + return Gates.generic( + { left: 0n, right: sx * sy, out: 0n, mul: 0n, const: -sz }, + { left: emptyCell(), right: y, out: emptyCell() } + ); + } + + if (isConst(xv) && isConst(yv) && isVar(zv)) { + let [sx, sy, [sz, z]] = [getConst(xv), getConst(yv), getVar(zv)]; + + // sz z - sx sy = 0 + return Gates.generic( + { left: 0n, right: 0n, out: sz, mul: 0n, const: -sx * sy }, + { left: emptyCell(), right: emptyCell(), out: z } + ); + } + + // three constants + + if (isConst(xv) && isConst(yv) && isConst(zv)) { + let [sx, sy, sz] = [getConst(xv), getConst(yv), getConst(zv)]; + + assert( + Fp.equal(Fp.mul(sx, sy), sz), + `assertMul(): ${sx} * ${sy} !== ${sz}` + ); + return; + } + + // sadly TS doesn't know that this was exhaustive + assert(false, `assertMul(): unreachable`); +} + +/** + * Assert square, `x^2 === z` + */ +function assertSquareCompatible(x: Field, z: Field) { + let xv = reduceToScaledVar(x); + let zv = reduceToScaledVar(z); + + if (isVar(xv) && isVar(zv)) { + let [[sx, x], [sz, z]] = [getVar(xv), getVar(zv)]; + + // -sz * z + (sx)^2 * x*x = 0 + return Gates.generic( + { left: 0n, right: 0n, out: -sz, mul: sx ** 2n, const: 0n }, + { left: x, right: x, out: z } + ); + } + + if (isVar(xv) && isConst(zv)) { + let [[sx, x], sz] = [getVar(xv), getConst(zv)]; + + // (sx)^2 * x*x - sz = 0 + return Gates.generic( + { left: 0n, right: 0n, out: 0n, mul: sx ** 2n, const: -sz }, + { left: x, right: x, out: emptyCell() } + ); + } + + if (isConst(xv) && isVar(zv)) { + let [sx, [sz, z]] = [getConst(xv), getVar(zv)]; + + // sz * z - (sx)^2 = 0 + return Gates.generic( + { left: 0n, right: 0n, out: sz, mul: 0n, const: -(sx ** 2n) }, + { left: emptyCell(), right: emptyCell(), out: z } + ); + } + + if (isConst(xv) && isConst(zv)) { + let [sx, sz] = [getConst(xv), getConst(zv)]; + + assert(Fp.equal(Fp.square(sx), sz), `assertSquare(): ${sx}^2 !== ${sz}`); + return; + } + + // sadly TS doesn't know that this was exhaustive + assert(false, `assertSquare(): unreachable`); +} + +/** + * Assert that x is either 0 or 1, `x^2 === x` + */ +function assertBooleanCompatible(x: Field) { + let xv = reduceToScaledVar(x); + + if (isVar(xv)) { + let [s, x] = getVar(xv); + + // -s*x + s^2 * x^2 = 0 + return Gates.generic( + { left: -s, right: 0n, out: 0n, mul: s ** 2n, const: 0n }, + { left: x, right: x, out: emptyCell() } + ); + } + + let x0 = getConst(xv); + assert(Fp.equal(Fp.square(x0), x0), `assertBoolean(): ${x} is not 0 or 1`); +} + +/** + * Assert equality, `x === y` + */ +function assertEqualCompatible(x: Field | FieldVar, y: Field | FieldVar) { + // TODO not optimal for a case like `x + y === c*z`, + // where this reduces x + y and then is still not able to just use a wire + let yv = reduceToScaledVar(y); + let xv = reduceToScaledVar(x); + + if (isVar(xv) && isVar(yv)) { + let [[sx, x], [sy, y]] = [getVar(xv), getVar(yv)]; + + if (sx === sy) { + // x === y, so use a wire + return Snarky.field.assertEqual(x, y); + } + + // sx * x - sy * y = 0 + return Gates.generic( + { left: sx, right: -sy, out: 0n, mul: 0n, const: 0n }, + { left: x, right: y, out: emptyCell() } + ); + } + + if (isVar(xv) && isConst(yv)) { + let [[sx, x], sy] = [getVar(xv), getConst(yv)]; + + // x === sy / sx, call into snarky to use its constants table + return Snarky.field.assertEqual( + FieldVar.scale(sx, x), + FieldVar.constant(sy) + ); + } + + if (isConst(xv) && isVar(yv)) { + let [sx, [sy, y]] = [getConst(xv), getVar(yv)]; + + // sx / sy === y, call into snarky to use its constants table + return Snarky.field.assertEqual( + FieldVar.constant(sx), + FieldVar.scale(sy, y) + ); + } + + if (isConst(xv) && isConst(yv)) { + let [sx, sy] = [getConst(xv), getConst(yv)]; + + assert(Fp.equal(sx, sy), `assertEqual(): ${sx} !== ${sy}`); + return; + } + + // sadly TS doesn't know that this was exhaustive + assert(false, `assertEqual(): unreachable`); +} diff --git a/src/lib/provable/gadgets/elliptic-curve.ts b/src/lib/provable/gadgets/elliptic-curve.ts new file mode 100644 index 0000000000..b4f2dd4666 --- /dev/null +++ b/src/lib/provable/gadgets/elliptic-curve.ts @@ -0,0 +1,734 @@ +import { inverse, mod } from '../../../bindings/crypto/finite-field.js'; +import { Field } from '../field.js'; +import { Provable } from '../provable.js'; +import { assert } from './common.js'; +import { Field3, ForeignField, split, weakBound } from './foreign-field.js'; +import { l2, multiRangeCheck } from './range-check.js'; +import { sha256 } from 'js-sha256'; +import { + bigIntToBytes, + bytesToBigInt, +} from '../../../bindings/crypto/bigint-helpers.js'; +import { + CurveAffine, + affineAdd, + affineDouble, +} from '../../../bindings/crypto/elliptic-curve.js'; +import { Bool } from '../bool.js'; +import { provable } from '../types/struct.js'; +import { assertPositiveInteger } from '../../../bindings/crypto/non-negative.js'; +import { arrayGet } from './basic.js'; +import { sliceField3 } from './bit-slices.js'; +import { Hashed } from '../packed.js'; +import { exists } from '../core/exists.js'; + +// external API +export { EllipticCurve, Point, Ecdsa }; + +// internal API +export { verifyEcdsaConstant, initialAggregator, simpleMapToCurve }; + +const EllipticCurve = { + add, + double, + negate, + assertOnCurve, + scale, + assertInSubgroup, + multiScalarMul, +}; + +/** + * Non-zero elliptic curve point in affine coordinates. + */ +type Point = { x: Field3; y: Field3 }; +type point = { x: bigint; y: bigint }; + +namespace Ecdsa { + /** + * ECDSA signature consisting of two curve scalars. + */ + export type Signature = { r: Field3; s: Field3 }; + export type signature = { r: bigint; s: bigint }; +} + +function add(p1: Point, p2: Point, Curve: { modulus: bigint }) { + let { x: x1, y: y1 } = p1; + let { x: x2, y: y2 } = p2; + let f = Curve.modulus; + + // constant case + if (Point.isConstant(p1) && Point.isConstant(p2)) { + let p3 = affineAdd(Point.toBigint(p1), Point.toBigint(p2), f); + return Point.from(p3); + } + + // witness and range-check slope, x3, y3 + let witnesses = exists(9, () => { + let [x1_, x2_, y1_, y2_] = Field3.toBigints(x1, x2, y1, y2); + let denom = inverse(mod(x1_ - x2_, f), f); + + let m = denom !== undefined ? mod((y1_ - y2_) * denom, f) : 0n; + let m2 = mod(m * m, f); + let x3 = mod(m2 - x1_ - x2_, f); + let y3 = mod(m * (x1_ - x3) - y1_, f); + + return [...split(m), ...split(x3), ...split(y3)]; + }); + let [m0, m1, m2, x30, x31, x32, y30, y31, y32] = witnesses; + let m: Field3 = [m0, m1, m2]; + let x3: Field3 = [x30, x31, x32]; + let y3: Field3 = [y30, y31, y32]; + ForeignField.assertAlmostReduced([m, x3, y3], f); + + // (x1 - x2)*m = y1 - y2 + let deltaX = ForeignField.Sum(x1).sub(x2); + let deltaY = ForeignField.Sum(y1).sub(y2); + ForeignField.assertMul(deltaX, m, deltaY, f); + + // m^2 = x1 + x2 + x3 + let xSum = ForeignField.Sum(x1).add(x2).add(x3); + ForeignField.assertMul(m, m, xSum, f); + + // (x1 - x3)*m = y1 + y3 + let deltaX1X3 = ForeignField.Sum(x1).sub(x3); + let ySum = ForeignField.Sum(y1).add(y3); + ForeignField.assertMul(deltaX1X3, m, ySum, f); + + return { x: x3, y: y3 }; +} + +function double(p1: Point, Curve: { modulus: bigint; a: bigint }) { + let { x: x1, y: y1 } = p1; + let f = Curve.modulus; + + // constant case + if (Point.isConstant(p1)) { + let p3 = affineDouble(Point.toBigint(p1), f); + return Point.from(p3); + } + + // witness and range-check slope, x3, y3 + let witnesses = exists(9, () => { + let [x1_, y1_] = Field3.toBigints(x1, y1); + let denom = inverse(mod(2n * y1_, f), f); + + let m = denom !== undefined ? mod(3n * mod(x1_ ** 2n, f) * denom, f) : 0n; + let m2 = mod(m * m, f); + let x3 = mod(m2 - 2n * x1_, f); + let y3 = mod(m * (x1_ - x3) - y1_, f); + + return [...split(m), ...split(x3), ...split(y3)]; + }); + let [m0, m1, m2, x30, x31, x32, y30, y31, y32] = witnesses; + let m: Field3 = [m0, m1, m2]; + let x3: Field3 = [x30, x31, x32]; + let y3: Field3 = [y30, y31, y32]; + ForeignField.assertAlmostReduced([m, x3, y3], f); + + // x1^2 = x1x1 + let x1x1 = ForeignField.mul(x1, x1, f); + + // 2*y1*m = 3*x1x1 + a + let y1Times2 = ForeignField.Sum(y1).add(y1); + let x1x1Times3PlusA = ForeignField.Sum(x1x1).add(x1x1).add(x1x1); + if (Curve.a !== 0n) + x1x1Times3PlusA = x1x1Times3PlusA.add(Field3.from(Curve.a)); + ForeignField.assertMul(y1Times2, m, x1x1Times3PlusA, f); + + // m^2 = 2*x1 + x3 + let xSum = ForeignField.Sum(x1).add(x1).add(x3); + ForeignField.assertMul(m, m, xSum, f); + + // (x1 - x3)*m = y1 + y3 + let deltaX1X3 = ForeignField.Sum(x1).sub(x3); + let ySum = ForeignField.Sum(y1).add(y3); + ForeignField.assertMul(deltaX1X3, m, ySum, f); + + return { x: x3, y: y3 }; +} + +function negate({ x, y }: Point, Curve: { modulus: bigint }) { + return { x, y: ForeignField.negate(y, Curve.modulus) }; +} + +function assertOnCurve( + p: Point, + { modulus: f, a, b }: { modulus: bigint; b: bigint; a: bigint } +) { + let { x, y } = p; + let x2 = ForeignField.mul(x, x, f); + let y2 = ForeignField.mul(y, y, f); + let y2MinusB = ForeignField.Sum(y2).sub(Field3.from(b)); + + // (x^2 + a) * x = y^2 - b + let x2PlusA = ForeignField.Sum(x2); + if (a !== 0n) x2PlusA = x2PlusA.add(Field3.from(a)); + let message: string | undefined; + if (Point.isConstant(p)) { + message = `assertOnCurve(): (${x}, ${y}) is not on the curve.`; + } + ForeignField.assertMul(x2PlusA, x, y2MinusB, f, message); +} + +/** + * EC scalar multiplication, `scalar*point` + * + * The result is constrained to be not zero. + */ +function scale( + scalar: Field3, + point: Point, + Curve: CurveAffine, + config: { + mode?: 'assert-nonzero' | 'assert-zero'; + windowSize?: number; + multiples?: Point[]; + } = { mode: 'assert-nonzero' } +) { + config.windowSize ??= Point.isConstant(point) ? 4 : 3; + return multiScalarMul([scalar], [point], Curve, [config], config.mode); +} + +// checks whether the elliptic curve point g is in the subgroup defined by [order]g = 0 +function assertInSubgroup(p: Point, Curve: CurveAffine) { + if (!Curve.hasCofactor) return; + scale(Field3.from(Curve.order), p, Curve, { mode: 'assert-zero' }); +} + +// check whether a point equals a constant point +// TODO implement the full case of two vars +function equals(p1: Point, p2: point, Curve: { modulus: bigint }) { + let xEquals = ForeignField.equals(p1.x, p2.x, Curve.modulus); + let yEquals = ForeignField.equals(p1.y, p2.y, Curve.modulus); + return xEquals.and(yEquals); +} + +/** + * Verify an ECDSA signature. + * + * Details about the `config` parameter: + * - For both the generator point `G` and public key `P`, `config` allows you to specify: + * - the `windowSize` which is used in scalar multiplication for this point. + * this flexibility is good because the optimal window size is different for constant and non-constant points. + * empirically, `windowSize=4` for constants and 3 for variables leads to the fewest constraints. + * our defaults reflect that the generator is always constant and the public key is variable in typical applications. + * - a table of multiples of those points, of length `2^windowSize`, which is used in the scalar multiplication gadget to speed up the computation. + * if these are not provided, they are computed on the fly. + * for the constant G, computing multiples costs no constraints, so passing them in makes no real difference. + * for variable public key, there is a possible use case: if the public key is a public input, then its multiples could also be. + * in that case, passing them in would avoid computing them in-circuit and save a few constraints. + * - The initial aggregator `ia`, see {@link initialAggregator}. By default, `ia` is computed deterministically on the fly. + */ +function verifyEcdsa( + Curve: CurveAffine, + signature: Ecdsa.Signature, + msgHash: Field3, + publicKey: Point, + config: { + G?: { windowSize: number; multiples?: Point[] }; + P?: { windowSize: number; multiples?: Point[] }; + ia?: point; + } = { G: { windowSize: 4 }, P: { windowSize: 4 } } +) { + // constant case + if ( + EcdsaSignature.isConstant(signature) && + Field3.isConstant(msgHash) && + Point.isConstant(publicKey) + ) { + let isValid = verifyEcdsaConstant( + Curve, + EcdsaSignature.toBigint(signature), + Field3.toBigint(msgHash), + Point.toBigint(publicKey) + ); + return new Bool(isValid); + } + + // provable case + // note: usually we don't check validity of inputs, like that the public key is a valid curve point + // we make an exception for the two non-standard conditions r != 0 and s != 0, + // which are unusual to capture in types and could be considered part of the verification algorithm + let { r, s } = signature; + ForeignField.inv(r, Curve.order); // proves r != 0 (important, because r = 0 => u2 = 0 kills the private key contribution) + let sInv = ForeignField.inv(s, Curve.order); // proves s != 0 + let u1 = ForeignField.mul(msgHash, sInv, Curve.order); + let u2 = ForeignField.mul(r, sInv, Curve.order); + + let G = Point.from(Curve.one); + let R = multiScalarMul( + [u1, u2], + [G, publicKey], + Curve, + config && [config.G, config.P], + 'assert-nonzero', + config?.ia + ); + // this ^ already proves that R != 0 (part of ECDSA verification) + + // reduce R.x modulo the curve order + let Rx = ForeignField.mul(R.x, Field3.from(1n), Curve.order); + + // we have to prove that Rx is canonical, because we check signature validity based on whether Rx _exactly_ equals the input r. + // if we allowed non-canonical Rx, the prover could make verify() return false on a valid signature, by adding a multiple of `Curve.order` to Rx. + ForeignField.assertLessThan(Rx, Curve.order); + + return Provable.equal(Field3.provable, Rx, r); +} + +/** + * Bigint implementation of ECDSA verify + */ +function verifyEcdsaConstant( + Curve: CurveAffine, + { r, s }: Ecdsa.signature, + msgHash: bigint, + publicKey: point +) { + let pk = Curve.from(publicKey); + if (Curve.equal(pk, Curve.zero)) return false; + if (Curve.hasCofactor && !Curve.isInSubgroup(pk)) return false; + if (r < 1n || r >= Curve.order) return false; + if (s < 1n || s >= Curve.order) return false; + + let sInv = Curve.Scalar.inverse(s); + assert(sInv !== undefined); + let u1 = Curve.Scalar.mul(msgHash, sInv); + let u2 = Curve.Scalar.mul(r, sInv); + + let R = Curve.add(Curve.scale(Curve.one, u1), Curve.scale(pk, u2)); + if (Curve.equal(R, Curve.zero)) return false; + + return Curve.Scalar.equal(R.x, r); +} + +/** + * Multi-scalar multiplication: + * + * s_0 * P_0 + ... + s_(n-1) * P_(n-1) + * + * where P_i are any points. + * + * By default, we prove that the result is not zero. + * + * If you set the `mode` parameter to `'assert-zero'`, on the other hand, + * we assert that the result is zero and just return the constant zero point. + * + * Implementation: We double all points together and leverage a precomputed table of size 2^c to avoid all but every cth addition. + * + * Note: this algorithm targets a small number of points, like 2 needed for ECDSA verification. + * + * TODO: could use lookups for picking precomputed multiples, instead of O(2^c) provable switch + * TODO: custom bit representation for the scalar that avoids 0, to get rid of the degenerate addition case + */ +function multiScalarMul( + scalars: Field3[], + points: Point[], + Curve: CurveAffine, + tableConfigs: ( + | { windowSize?: number; multiples?: Point[] } + | undefined + )[] = [], + mode: 'assert-nonzero' | 'assert-zero' = 'assert-nonzero', + ia?: point +): Point { + let n = points.length; + assert(scalars.length === n, 'Points and scalars lengths must match'); + assertPositiveInteger(n, 'Expected at least 1 point and scalar'); + let useGlv = Curve.hasEndomorphism; + + // constant case + if (scalars.every(Field3.isConstant) && points.every(Point.isConstant)) { + // TODO dedicated MSM + let s = scalars.map(Field3.toBigint); + let P = points.map(Point.toBigint); + let sum = Curve.zero; + for (let i = 0; i < n; i++) { + if (useGlv) { + sum = Curve.add(sum, Curve.Endo.scale(P[i], s[i])); + } else { + sum = Curve.add(sum, Curve.scale(P[i], s[i])); + } + } + if (mode === 'assert-zero') { + assert(sum.infinity, 'scalar multiplication: expected zero result'); + return Point.from(Curve.zero); + } + assert(!sum.infinity, 'scalar multiplication: expected non-zero result'); + return Point.from(sum); + } + + // parse or build point tables + let windowSizes = points.map((_, i) => tableConfigs[i]?.windowSize ?? 1); + let tables = points.map((P, i) => + getPointTable(Curve, P, windowSizes[i], tableConfigs[i]?.multiples) + ); + + let maxBits = Curve.Scalar.sizeInBits; + + if (useGlv) { + maxBits = Curve.Endo.decomposeMaxBits; + + // decompose scalars and handle signs + let n2 = 2 * n; + let scalars2: Field3[] = Array(n2); + let points2: Point[] = Array(n2); + let windowSizes2: number[] = Array(n2); + let tables2: Point[][] = Array(n2); + let mrcStack: Field[] = []; + + for (let i = 0; i < n; i++) { + let [s0, s1] = decomposeNoRangeCheck(Curve, scalars[i]); + scalars2[2 * i] = s0.abs; + scalars2[2 * i + 1] = s1.abs; + + let table = tables[i]; + let endoTable = table.map((P, i) => { + if (i === 0) return P; + let [phiP, betaXBound] = endomorphism(Curve, P); + mrcStack.push(betaXBound); + return phiP; + }); + tables2[2 * i] = table.map((P) => + negateIf(s0.isNegative, P, Curve.modulus) + ); + tables2[2 * i + 1] = endoTable.map((P) => + negateIf(s1.isNegative, P, Curve.modulus) + ); + points2[2 * i] = tables2[2 * i][1]; + points2[2 * i + 1] = tables2[2 * i + 1][1]; + + windowSizes2[2 * i] = windowSizes2[2 * i + 1] = windowSizes[i]; + } + reduceMrcStack(mrcStack); + // from now on, everything is the same as if these were the original points and scalars + points = points2; + tables = tables2; + scalars = scalars2; + windowSizes = windowSizes2; + n = n2; + } + + // slice scalars + let scalarChunks = scalars.map((s, i) => + sliceField3(s, { maxBits, chunkSize: windowSizes[i] }) + ); + + // hash points to make array access more efficient + // a Point is 6 field elements, the hash is just 1 field element + const HashedPoint = Hashed.create(Point.provable); + + let hashedTables = tables.map((table) => + table.map((point) => HashedPoint.hash(point)) + ); + + ia ??= initialAggregator(Curve); + let sum = Point.from(ia); + + for (let i = maxBits - 1; i >= 0; i--) { + // add in multiple of each point + for (let j = 0; j < n; j++) { + let windowSize = windowSizes[j]; + if (i % windowSize === 0) { + // pick point to add based on the scalar chunk + let sj = scalarChunks[j][i / windowSize]; + let sjP = + windowSize === 1 + ? points[j] + : arrayGetGeneric( + HashedPoint.provable, + hashedTables[j], + sj + ).unhash(); + + // ec addition + let added = add(sum, sjP, Curve); + + // handle degenerate case (if sj = 0, Gj is all zeros and the add result is garbage) + sum = Provable.if(sj.equals(0), Point.provable, sum, added); + } + } + + if (i === 0) break; + + // jointly double all points + // (note: the highest couple of bits will not create any constraints because sum is constant; no need to handle that explicitly) + sum = double(sum, Curve); + } + + // the sum is now 2^(b-1)*IA + sum_i s_i*P_i + // we assert that sum != 2^(b-1)*IA, and add -2^(b-1)*IA to get our result + let iaFinal = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(maxBits - 1)); + let isZero = equals(sum, iaFinal, Curve); + + if (mode === 'assert-nonzero') { + isZero.assertFalse(); + sum = add(sum, Point.from(Curve.negate(iaFinal)), Curve); + } else { + isZero.assertTrue(); + // for type consistency with the 'assert-nonzero' case + sum = Point.from(Curve.zero); + } + + return sum; +} + +function negateIf(condition: Field, P: Point, f: bigint) { + let y = Provable.if( + Bool.Unsafe.fromField(condition), + Field3.provable, + ForeignField.negate(P.y, f), + P.y + ); + return { x: P.x, y }; +} + +function endomorphism(Curve: CurveAffine, P: Point) { + let beta = Field3.from(Curve.Endo.base); + let betaX = ForeignField.mul(beta, P.x, Curve.modulus); + return [{ x: betaX, y: P.y }, weakBound(betaX[2], Curve.modulus)] as const; +} + +/** + * Decompose s = s0 + s1*lambda where s0, s1 are guaranteed to be small + * + * Note: This assumes that s0 and s1 are range-checked externally; in scalar multiplication this happens because they are split into chunks. + */ +function decomposeNoRangeCheck(Curve: CurveAffine, s: Field3) { + assert( + Curve.Endo.decomposeMaxBits < l2, + 'decomposed scalars assumed to be < 2*88 bits' + ); + // witness s0, s1 + let witnesses = exists(6, () => { + let [s0, s1] = Curve.Endo.decompose(Field3.toBigint(s)); + let [s00, s01] = split(s0.abs); + let [s10, s11] = split(s1.abs); + // prettier-ignore + return [ + s0.isNegative ? 1n : 0n, s00, s01, + s1.isNegative ? 1n : 0n, s10, s11, + ]; + }); + let [s0Negative, s00, s01, s1Negative, s10, s11] = witnesses; + // we can hard-code highest limb to zero + // (in theory this would allow us to hard-code the high quotient limb to zero in the ffmul below, and save 2 RCs.. but not worth it) + let s0: Field3 = [s00, s01, Field.from(0n)]; + let s1: Field3 = [s10, s11, Field.from(0n)]; + s0Negative.assertBool(); + s1Negative.assertBool(); + + // prove that s1*lambda = s - s0 + let lambda = Provable.if( + Bool.Unsafe.fromField(s1Negative), + Field3.provable, + Field3.from(Curve.Scalar.negate(Curve.Endo.scalar)), + Field3.from(Curve.Endo.scalar) + ); + let rhs = Provable.if( + Bool.Unsafe.fromField(s0Negative), + Field3.provable, + ForeignField.Sum(s).add(s0).finish(Curve.order), + ForeignField.Sum(s).sub(s0).finish(Curve.order) + ); + ForeignField.assertMul(s1, lambda, rhs, Curve.order); + + return [ + { isNegative: s0Negative, abs: s0 }, + { isNegative: s1Negative, abs: s1 }, + ] as const; +} + +/** + * Sign a message hash using ECDSA. + */ +function signEcdsa(Curve: CurveAffine, msgHash: bigint, privateKey: bigint) { + let { Scalar } = Curve; + let k = Scalar.random(); + let R = Curve.scale(Curve.one, k); + let r = Scalar.mod(R.x); + let kInv = Scalar.inverse(k); + assert(kInv !== undefined); + let s = Scalar.mul(kInv, Scalar.add(msgHash, Scalar.mul(r, privateKey))); + return { r, s }; +} + +/** + * Given a point P, create the list of multiples [0, P, 2P, 3P, ..., (2^windowSize-1) * P]. + * This method is provable, but won't create any constraints given a constant point. + */ +function getPointTable( + Curve: CurveAffine, + P: Point, + windowSize: number, + table?: Point[] +): Point[] { + assertPositiveInteger(windowSize, 'invalid window size'); + let n = 1 << windowSize; // n >= 2 + + assert(table === undefined || table.length === n, 'invalid table'); + if (table !== undefined) return table; + + table = [Point.from(Curve.zero), P]; + if (n === 2) return table; + + let Pi = double(P, Curve); + table.push(Pi); + for (let i = 3; i < n; i++) { + Pi = add(Pi, P, Curve); + table.push(Pi); + } + return table; +} + +/** + * For EC scalar multiplication we use an initial point which is subtracted + * at the end, to avoid encountering the point at infinity. + * + * This is a simple hash-to-group algorithm which finds that initial point. + * It's important that this point has no known discrete logarithm so that nobody + * can create an invalid proof of EC scaling. + */ +function initialAggregator(Curve: CurveAffine) { + // hash that identifies the curve + let h = sha256.create(); + h.update('initial-aggregator'); + h.update(bigIntToBytes(Curve.modulus)); + h.update(bigIntToBytes(Curve.order)); + h.update(bigIntToBytes(Curve.a)); + h.update(bigIntToBytes(Curve.b)); + let bytes = h.array(); + + // bytes represent a 256-bit number + // use that as x coordinate + const F = Curve.Field; + let x = F.mod(bytesToBigInt(bytes)); + return simpleMapToCurve(x, Curve); +} + +function random(Curve: CurveAffine) { + let x = Curve.Field.random(); + return simpleMapToCurve(x, Curve); +} + +/** + * Given an x coordinate (base field element), increment it until we find one with + * a y coordinate that satisfies the curve equation, and return the point. + * + * If the curve has a cofactor, multiply by it to get a point in the correct subgroup. + */ +function simpleMapToCurve(x: bigint, Curve: CurveAffine) { + const F = Curve.Field; + let y: bigint | undefined = undefined; + + // increment x until we find a y coordinate + while (y === undefined) { + x = F.add(x, 1n); + // solve y^2 = x^3 + ax + b + let x3 = F.mul(F.square(x), x); + let y2 = F.add(x3, F.mul(Curve.a, x) + Curve.b); + y = F.sqrt(y2); + } + let p = { x, y, infinity: false }; + + // clear cofactor + if (Curve.hasCofactor) { + p = Curve.scale(p, Curve.cofactor!); + } + return p; +} + +/** + * Get value from array in O(n) constraints. + * + * Assumes that index is in [0, n), returns an unconstrained result otherwise. + */ +function arrayGetGeneric(type: Provable, array: T[], index: Field) { + // witness result + let a = Provable.witness(type, () => array[Number(index)]); + let aFields = type.toFields(a); + + // constrain each field of the result + let size = type.sizeInFields(); + let arrays = array.map(type.toFields); + + for (let j = 0; j < size; j++) { + let arrayFieldsJ = arrays.map((x) => x[j]); + arrayGet(arrayFieldsJ, index).assertEquals(aFields[j]); + } + return a; +} + +// type/conversion helpers + +const Point = { + from({ x, y }: point): Point { + return { x: Field3.from(x), y: Field3.from(y) }; + }, + toBigint({ x, y }: Point) { + return { x: Field3.toBigint(x), y: Field3.toBigint(y), infinity: false }; + }, + isConstant: (P: Point) => Provable.isConstant(Point.provable, P), + + /** + * Random point on the curve. + */ + random(Curve: CurveAffine) { + return Point.from(random(Curve)); + }, + + provable: provable({ x: Field3.provable, y: Field3.provable }), +}; + +const EcdsaSignature = { + from({ r, s }: Ecdsa.signature): Ecdsa.Signature { + return { r: Field3.from(r), s: Field3.from(s) }; + }, + toBigint({ r, s }: Ecdsa.Signature): Ecdsa.signature { + return { r: Field3.toBigint(r), s: Field3.toBigint(s) }; + }, + isConstant: (S: Ecdsa.Signature) => + Provable.isConstant(EcdsaSignature.provable, S), + + /** + * Create an {@link EcdsaSignature} from a raw 130-char hex string as used in + * [Ethereum transactions](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope). + */ + fromHex(rawSignature: string): Ecdsa.Signature { + let prefix = rawSignature.slice(0, 2); + let signature = rawSignature.slice(2, 130); + if (prefix !== '0x' || signature.length < 128) { + throw Error( + `Signature.fromHex(): Invalid signature, expected hex string 0x... of length at least 130.` + ); + } + let r = BigInt(`0x${signature.slice(0, 64)}`); + let s = BigInt(`0x${signature.slice(64)}`); + return EcdsaSignature.from({ r, s }); + }, + + provable: provable({ r: Field3.provable, s: Field3.provable }), +}; + +const Ecdsa = { + sign: signEcdsa, + verify: verifyEcdsa, + Signature: EcdsaSignature, +}; + +// MRC stack + +function reduceMrcStack(xs: Field[]) { + let n = xs.length; + let nRemaining = n % 3; + let nFull = (n - nRemaining) / 3; + for (let i = 0; i < nFull; i++) { + multiRangeCheck([xs[3 * i], xs[3 * i + 1], xs[3 * i + 2]]); + } + let remaining: Field3 = [Field.from(0n), Field.from(0n), Field.from(0n)]; + for (let i = 0; i < nRemaining; i++) { + remaining[i] = xs[3 * nFull + i]; + } + multiRangeCheck(remaining); +} diff --git a/src/lib/provable/gadgets/foreign-field.ts b/src/lib/provable/gadgets/foreign-field.ts new file mode 100644 index 0000000000..df864a7a6b --- /dev/null +++ b/src/lib/provable/gadgets/foreign-field.ts @@ -0,0 +1,794 @@ +/** + * Foreign field arithmetic gadgets. + */ +import { + inverse as modInverse, + mod, +} from '../../../bindings/crypto/finite-field.js'; +import { provableTuple } from '../types/provable-derivers.js'; +import { Unconstrained } from '../types/unconstrained.js'; +import type { Field } from '../field.js'; +import { Gates, foreignFieldAdd } from '../gates.js'; +import { exists } from '../core/exists.js'; +import { modifiedField } from '../types/fields.js'; +import { Tuple, TupleN } from '../../util/types.js'; +import { assertOneOf } from './basic.js'; +import { assert, bitSlice, toVar, toVars } from './common.js'; +import { + l, + lMask, + multiRangeCheck, + l2, + l2Mask, + l3, + compactMultiRangeCheck, +} from './range-check.js'; +import { createBool, createField } from '../core/field-constructor.js'; + +// external API +export { ForeignField, Field3 }; + +// internal API +export { bigint3, Sign, split, combine, weakBound, Sum, assertMul }; + +/** + * A 3-tuple of Fields, representing a 3-limb bigint. + */ +type Field3 = [Field, Field, Field]; +type bigint3 = [bigint, bigint, bigint]; +type Sign = -1n | 1n; + +const ForeignField = { + add(x: Field3, y: Field3, f: bigint) { + return sum([x, y], [1n], f); + }, + sub(x: Field3, y: Field3, f: bigint) { + return sum([x, y], [-1n], f); + }, + negate(x: Field3, f: bigint) { + return sum([Field3.from(0n), x], [-1n], f); + }, + sum, + Sum(x: Field3) { + return new Sum(x); + }, + + mul: multiply, + inv: inverse, + div: divide, + assertMul, + + assertAlmostReduced, + + assertLessThan, + assertLessThanOrEqual, + + equals, +}; + +/** + * computes x[0] + sign[0] * x[1] + ... + sign[n-1] * x[n] modulo f + * + * assumes that inputs are range checked, does range check on the result. + */ +function sum(x: Field3[], sign: Sign[], f: bigint) { + assert(x.length === sign.length + 1, 'inputs and operators match'); + + // constant case + if (x.every(Field3.isConstant)) { + let xBig = x.map(Field3.toBigint); + let sum = sign.reduce((sum, s, i) => sum + s * xBig[i + 1], xBig[0]); + return Field3.from(mod(sum, f)); + } + // provable case - create chain of ffadd rows + x = x.map(toVars); + let result = x[0]; + for (let i = 0; i < sign.length; i++) { + ({ result } = singleAdd(result, x[i + 1], sign[i], f)); + } + // final zero row to hold result + Gates.zero(...result); + + // range check result + indirectMultiRangeChange(result); + + return result; +} + +/** + * core building block for non-native addition + * + * **warning**: this just adds the `foreignFieldAdd` row; + * it _must_ be chained with a second row that holds the result in its first 3 cells. + * + * the second row could, for example, be `zero`, `foreignFieldMul`, or another `foreignFieldAdd`. + */ +function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { + let f_ = split(f); + + let [r0, r1, r2, overflow, carry] = exists(5, () => { + let x_ = toBigint3(x); + let y_ = toBigint3(y); + + // figure out if there's overflow + let r = combine(x_) + sign * combine(y_); + let overflow = 0n; + if (sign === 1n && r >= f) overflow = 1n; + if (sign === -1n && r < 0n) overflow = -1n; + if (f === 0n) overflow = 0n; // special case where overflow doesn't change anything + + // do the add with carry + // note: this "just works" with negative r01 + let r01 = combine2(x_) + sign * combine2(y_) - overflow * combine2(f_); + let carry = r01 >> l2; + r01 &= l2Mask; + let [r0, r1] = split2(r01); + let r2 = x_[2] + sign * y_[2] - overflow * f_[2] + carry; + + return [r0, r1, r2, overflow, carry]; + }); + + foreignFieldAdd({ left: x, right: y, overflow, carry, modulus: f_, sign }); + + return { result: [r0, r1, r2] satisfies Field3, overflow }; +} + +function multiply(a: Field3, b: Field3, f: bigint): Field3 { + assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); + + // constant case + if (Field3.isConstant(a) && Field3.isConstant(b)) { + let ab = Field3.toBigint(a) * Field3.toBigint(b); + return Field3.from(mod(ab, f)); + } + + // provable case + let { r01, r2, q } = multiplyNoRangeCheck(a, b, f); + + // limb range checks on quotient and remainder + multiRangeCheck(q); + let r = compactMultiRangeCheck(r01, r2); + return r; +} + +function inverse(x: Field3, f: bigint): Field3 { + assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); + + // constant case + if (Field3.isConstant(x)) { + let xInv = modInverse(Field3.toBigint(x), f); + assert(xInv !== undefined, 'inverse exists'); + return Field3.from(xInv); + } + + // provable case + let xInv = exists(3, () => { + let xInv = modInverse(Field3.toBigint(x), f); + return xInv === undefined ? [0n, 0n, 0n] : split(xInv); + }); + multiRangeCheck(xInv); + // we need to bound xInv because it's a multiplication input + let xInv2Bound = weakBound(xInv[2], f); + + let one: Field2 = [createField(1n), createField(0n)]; + assertMulInternal(x, xInv, one, f); + + // range check on result bound + // TODO: this uses two RCs too many.. need global RC stack + multiRangeCheck([xInv2Bound, createField(0n), createField(0n)]); + + return xInv; +} + +function divide( + x: Field3, + y: Field3, + f: bigint, + { allowZeroOverZero = false } = {} +) { + assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); + + // constant case + if (Field3.isConstant(x) && Field3.isConstant(y)) { + let yInv = modInverse(Field3.toBigint(y), f); + assert(yInv !== undefined, 'inverse exists'); + return Field3.from(mod(Field3.toBigint(x) * yInv, f)); + } + + // provable case + // to show that z = x/y, we prove that z*y = x and y != 0 (the latter avoids the unconstrained 0/0 case) + let z = exists(3, () => { + let yInv = modInverse(Field3.toBigint(y), f); + if (yInv === undefined) return [0n, 0n, 0n]; + return split(mod(Field3.toBigint(x) * yInv, f)); + }); + multiRangeCheck(z); + let z2Bound = weakBound(z[2], f); + assertMulInternal(z, y, x, f); + + // range check on result bound + multiRangeCheck([z2Bound, createField(0n), createField(0n)]); + + if (!allowZeroOverZero) { + ForeignField.equals(y, 0n, f).assertFalse(); + } + return z; +} + +/** + * Common logic for gadgets that expect a certain multiplication result a priori, instead of just using the remainder. + */ +function assertMulInternal( + x: Field3, + y: Field3, + xy: Field3 | Field2, + f: bigint, + message?: string +) { + let { r01, r2, q } = multiplyNoRangeCheck(x, y, f); + + // range check on quotient + multiRangeCheck(q); + + // bind remainder to input xy + if (xy.length === 2) { + let [xy01, xy2] = xy; + r01.assertEquals(xy01, message); + r2.assertEquals(xy2, message); + } else { + let xy01 = xy[0].add(xy[1].mul(1n << l)); + r01.assertEquals(xy01, message); + r2.assertEquals(xy[2], message); + } +} + +/** + * Core building block for all gadgets using foreign field multiplication. + */ +function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { + // notation follows https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md + let f_ = (1n << l3) - f; + let [f_0, f_1, f_2] = split(f_); + let f2 = f >> l2; + let f2Bound = (1n << l) - f2 - 1n; + + let witnesses = exists(21, () => { + // convert inputs to bigints + let [a0, a1, a2] = toBigint3(a); + let [b0, b1, b2] = toBigint3(b); + + // compute q and r such that a*b = q*f + r + let ab = combine([a0, a1, a2]) * combine([b0, b1, b2]); + let q = ab / f; + let r = ab - q * f; + + let [q0, q1, q2] = split(q); + let [r0, r1, r2] = split(r); + let r01 = combine2([r0, r1]); + + // compute product terms + let p0 = a0 * b0 + q0 * f_0; + let p1 = a0 * b1 + a1 * b0 + q0 * f_1 + q1 * f_0; + let p2 = a0 * b2 + a1 * b1 + a2 * b0 + q0 * f_2 + q1 * f_1 + q2 * f_0; + + let [p10, p110, p111] = split(p1); + let p11 = combine2([p110, p111]); + + // carry bottom limbs + let c0 = (p0 + (p10 << l) - r01) >> l2; + + // carry top limb + let c1 = (p2 - r2 + p11 + c0) >> l; + + // split high carry + let c1_00 = bitSlice(c1, 0, 12); + let c1_12 = bitSlice(c1, 12, 12); + let c1_24 = bitSlice(c1, 24, 12); + let c1_36 = bitSlice(c1, 36, 12); + let c1_48 = bitSlice(c1, 48, 12); + let c1_60 = bitSlice(c1, 60, 12); + let c1_72 = bitSlice(c1, 72, 12); + let c1_84 = bitSlice(c1, 84, 2); + let c1_86 = bitSlice(c1, 86, 2); + let c1_88 = bitSlice(c1, 88, 2); + let c1_90 = bitSlice(c1, 90, 1); + + // quotient high bound + let q2Bound = q2 + f2Bound; + + // prettier-ignore + return [ + r01, r2, + q0, q1, q2, + q2Bound, + p10, p110, p111, + c0, + c1_00, c1_12, c1_24, c1_36, c1_48, c1_60, c1_72, + c1_84, c1_86, c1_88, c1_90, + ]; + }); + + // prettier-ignore + let [ + r01, r2, + q0, q1, q2, + q2Bound, + p10, p110, p111, + c0, + c1_00, c1_12, c1_24, c1_36, c1_48, c1_60, c1_72, + c1_84, c1_86, c1_88, c1_90, + ] = witnesses; + + let q: Field3 = [q0, q1, q2]; + + // ffmul gate. this already adds the following zero row. + Gates.foreignFieldMul({ + left: a, + right: b, + remainder: [r01, r2], + quotient: q, + quotientHiBound: q2Bound, + product1: [p10, p110, p111], + carry0: c0, + carry1p: [c1_00, c1_12, c1_24, c1_36, c1_48, c1_60, c1_72], + carry1c: [c1_84, c1_86, c1_88, c1_90], + foreignFieldModulus2: f2, + negForeignFieldModulus: [f_0, f_1, f_2], + }); + + // multi-range check on internal values + multiRangeCheck([p10, p110, q2Bound]); + + // note: this function is supposed to be the most flexible interface to the ffmul gate. + // that's why we don't add range checks on q and r here, because there are valid use cases + // for not range-checking either of them -- for example, they could be wired to other + // variables that are already range-checked, or to constants / public inputs. + return { r01, r2, q }; +} + +function weakBound(x: Field, f: bigint) { + // if f0, f1 === 0, we can use a stronger bound x[2] < f2 + // because this is true for all field elements x in [0,f) + if ((f & l2Mask) === 0n) { + return x.add(lMask + 1n - (f >> l2)); + } + // otherwise, we use x[2] < f2 + 1, so we allow x[2] === f2 + return x.add(lMask - (f >> l2)); +} + +/** + * Apply range checks and weak bounds checks to a list of Field3s. + * Optimal if the list length is a multiple of 3. + */ +function assertAlmostReduced(xs: Field3[], f: bigint, skipMrc = false) { + let bounds: Field[] = []; + + for (let x of xs) { + if (!skipMrc) multiRangeCheck(x); + + bounds.push(weakBound(x[2], f)); + if (TupleN.hasLength(3, bounds)) { + multiRangeCheck(bounds); + bounds = []; + } + } + if (TupleN.hasLength(1, bounds)) { + multiRangeCheck([...bounds, createField(0n), createField(0n)]); + } + if (TupleN.hasLength(2, bounds)) { + multiRangeCheck([...bounds, createField(0n)]); + } +} + +/** + * check whether x = c mod f + * + * c is a constant, and we require c in [0, f) + * + * assumes that x is almost reduced mod f, so we know that x might be c or c + f, but not c + 2f, c + 3f, ... + */ +function equals(x: Field3, c: bigint, f: bigint) { + assert(c >= 0n && c < f, 'equals: c must be in [0, f)'); + + // constant case + if (Field3.isConstant(x)) { + return createBool(mod(Field3.toBigint(x), f) === c); + } + + // provable case + if (f >= 1n << l2) { + // check whether x = 0 or x = f + let x01 = toVar(x[0].add(x[1].mul(1n << l))); + let [c01, c2] = [c & l2Mask, c >> l2]; + let [cPlusF01, cPlusF2] = [(c + f) & l2Mask, (c + f) >> l2]; + + // (x01, x2) = (c01, c2) + let isC = x01.equals(c01).and(x[2].equals(c2)); + // (x01, x2) = (cPlusF01, cPlusF2) + let isCPlusF = x01.equals(cPlusF01).and(x[2].equals(cPlusF2)); + + return isC.or(isCPlusF); + } else { + // if f < 2^2l, the approach above doesn't work (we don't know from x[2] = 0 that x < 2f), + // so in that case we assert that x < f and then check whether it's equal to c + ForeignField.assertLessThan(x, f); + let x012 = toVar(x[0].add(x[1].mul(1n << l)).add(x[2].mul(1n << l2))); + return x012.equals(c); + } +} + +const provableLimb = modifiedField({ + toInput(x) { + return { packed: [[x, Number(l)]] }; + }, +}); + +const Field3 = { + /** + * Turn a bigint into a 3-tuple of Fields + */ + from(x: bigint | Field3): Field3 { + if (Array.isArray(x)) return x; + return Tuple.map(split(x), createField); + }, + + /** + * Turn a 3-tuple of Fields into a bigint + */ + toBigint(x: Field3): bigint { + return combine(toBigint3(x)); + }, + + /** + * Turn several 3-tuples of Fields into bigints + */ + toBigints>(...xs: T) { + return Tuple.map(xs, Field3.toBigint); + }, + + /** + * Check whether a 3-tuple of Fields is constant + */ + isConstant(x: Field3) { + return x.every((x) => x.isConstant()); + }, + + /** + * `Provable` interface for `Field3 = [Field, Field, Field]`. + * + * Note: Witnessing this creates a plain tuple of field elements without any implicit + * range checks. + */ + provable: provableTuple([provableLimb, provableLimb, provableLimb]), +}; + +type Field2 = [Field, Field]; +const Field2 = { + toBigint(x: Field2): bigint { + return combine2(Tuple.map(x, (x) => x.toBigInt())); + }, +}; + +function toBigint3(x: Field3): bigint3 { + return Tuple.map(x, (x) => x.toBigInt()); +} + +function combine([x0, x1, x2]: bigint3) { + return x0 + (x1 << l) + (x2 << l2); +} +function split(x: bigint): bigint3 { + return [x & lMask, (x >> l) & lMask, (x >> l2) & lMask]; +} + +function combine2([x0, x1]: bigint3 | [bigint, bigint]) { + return x0 + (x1 << l); +} +function split2(x: bigint): [bigint, bigint] { + return [x & lMask, (x >> l) & lMask]; +} + +/** + * Optimized multiplication of sums, like (x + y)*z = a + b + c + * + * We use several optimizations over naive summing and then multiplying: + * + * - we skip the range check on the remainder sum, because ffmul is sound with r being a sum of range-checked values + * - we replace the range check on the input sums with an extra low limb sum using generic gates + * - we chain the first input's sum into the ffmul gate + * + * As usual, all values are assumed to be range checked, and the left and right multiplication inputs + * are assumed to be bounded such that `l * r < 2^264 * (native modulus)`. + * However, all extra checks that are needed on the _sums_ are handled here. + */ +function assertMul( + x: Field3 | Sum, + y: Field3 | Sum, + xy: Field3 | Sum, + f: bigint, + message?: string +) { + x = Sum.fromUnfinished(x); + y = Sum.fromUnfinished(y); + xy = Sum.fromUnfinished(xy); + + // conservative estimate to ensure that multiplication bound is satisfied + // we assume that all summands si are bounded with si[2] <= f[2] checks, which implies si < 2^k where k := ceil(log(f)) + // our assertion below gives us + // |x|*|y| + q*f + |r| < (x.length * y.length) 2^2k + 2^2k + 2^2k < 3 * 2^(2*258) < 2^264 * (native modulus) + assert( + BigInt(Math.ceil(Math.sqrt(x.length * y.length))) * f < 1n << 258n, + `Foreign modulus is too large for multiplication of sums of lengths ${x.length} and ${y.length}` + ); + + // finish the y and xy sums with a zero gate + let y0 = y.finishForMulInput(f); + let xy0 = xy.finish(f); + + // x is chained into the ffmul gate + let x0 = x.finishForMulInput(f, true); + + // constant case + if ( + Field3.isConstant(x0) && + Field3.isConstant(y0) && + Field3.isConstant(xy0) + ) { + let x_ = Field3.toBigint(x0); + let y_ = Field3.toBigint(y0); + let xy_ = Field3.toBigint(xy0); + assert( + mod(x_ * y_, f) === xy_, + message ?? 'assertMul(): incorrect multiplication result' + ); + return; + } + + assertMulInternal(x0, y0, xy0, f, message); +} + +/** + * Lazy sum of {@link Field3} elements, which can be used as input to `Gadgets.ForeignField.assertMul()`. + */ +class Sum { + #result?: Field3; + #summands: Field3[]; + #ops: Sign[] = []; + + constructor(x: Field3) { + this.#summands = [x]; + } + + get result() { + assert(this.#result !== undefined, 'sum not finished'); + return this.#result; + } + + get length() { + return this.#summands.length; + } + + add(y: Field3) { + assert(this.#result === undefined, 'sum already finished'); + this.#ops.push(1n); + this.#summands.push(y); + return this; + } + + sub(y: Field3) { + assert(this.#result === undefined, 'sum already finished'); + this.#ops.push(-1n); + this.#summands.push(y); + return this; + } + + #return(x: Field3) { + this.#result = x; + return x; + } + + isConstant() { + return this.#summands.every(Field3.isConstant); + } + + finish(f: bigint, isChained = false) { + assert(this.#result === undefined, 'sum already finished'); + let signs = this.#ops; + let n = signs.length; + if (n === 0) return this.#return(this.#summands[0]); + + // constant case + if (this.isConstant()) { + return this.#return(sum(this.#summands, signs, f)); + } + + // provable case + let x = this.#summands.map(toVars); + let result = x[0]; + + for (let i = 0; i < n; i++) { + ({ result } = singleAdd(result, x[i + 1], signs[i], f)); + } + if (!isChained) Gates.zero(...result); + + this.#result = result; + return result; + } + + // TODO this is complex and should be removed once we fix the ffadd gate to constrain all limbs individually + finishForMulInput(f: bigint, isChained = false) { + assert(this.#result === undefined, 'sum already finished'); + let signs = this.#ops; + let n = signs.length; + if (n === 0) return this.#return(this.#summands[0]); + + // constant case + if (this.isConstant()) { + return this.#return(sum(this.#summands, signs, f)); + } + + // provable case + let xs = this.#summands.map(toVars); + + // since the sum becomes a multiplication input, we need to constrain all limbs _individually_. + // sadly, ffadd only constrains the low and middle limb together. + // we could fix it with a RC just for the lower two limbs + // but it's cheaper to add generic gates which handle the lowest limb separately, and avoids the unfilled MRC slot + let f0 = f & lMask; + + // generic gates for low limbs + let x0 = xs[0][0]; + let x0s: Field[] = []; + let overflows: Field[] = []; + let xRef = Unconstrained.witness(() => Field3.toBigint(xs[0])); + + // this loop mirrors the computation that a chain of ffadd gates does, + // but everything is done only on the lowest limb and using generic gates. + // the output is a sequence of low limbs (x0) and overflows, which will be wired to the ffadd results at each step. + for (let i = 0; i < n; i++) { + // compute carry and overflow + let [carry, overflow] = exists(2, () => { + // this duplicates some of the logic in singleAdd + let x = xRef.get(); + let x0 = x & lMask; + let xi = toBigint3(xs[i + 1]); + let sign = signs[i]; + + // figure out if there's overflow + x += sign * combine(xi); + let overflow = 0n; + if (sign === 1n && x >= f) overflow = 1n; + if (sign === -1n && x < 0n) overflow = -1n; + if (f === 0n) overflow = 0n; + xRef.set(x - overflow * f); + + // add with carry, only on the lowest limb + x0 = x0 + sign * xi[0] - overflow * f0; + let carry = x0 >> l; + return [carry, overflow]; + }); + overflows.push(overflow); + + // constrain carry + assertOneOf(carry, [0n, 1n, -1n]); + + // x0 <- x0 + s*xi0 - o*f0 - c*2^l + x0 = toVar( + x0 + .add(xs[i + 1][0].mul(signs[i])) + .sub(overflow.mul(f0)) + .sub(carry.mul(1n << l)) + ); + x0s.push(x0); + } + + // ffadd chain + let x = xs[0]; + for (let i = 0; i < n; i++) { + let { result, overflow } = singleAdd(x, xs[i + 1], signs[i], f); + // wire low limb and overflow to previous values + result[0].assertEquals(x0s[i]); + overflow.assertEquals(overflows[i]); + x = result; + } + if (!isChained) Gates.zero(...x); + + this.#result = x; + return x; + } + + rangeCheck() { + assert(this.#result !== undefined, 'sum not finished'); + if (this.#ops.length > 0) multiRangeCheck(this.#result); + } + + static fromUnfinished(x: Field3 | Sum) { + if (x instanceof Sum) { + assert(x.#result === undefined, 'sum already finished'); + return x; + } + return new Sum(x); + } +} + +// Field3 comparison + +function assertLessThan(x: Field3, y: bigint | Field3) { + let y_ = Field3.from(y); + + // constant case + + if (Field3.isConstant(x) && Field3.isConstant(y_)) { + assert( + Field3.toBigint(x) < Field3.toBigint(y_), + 'assertLessThan: got x >= y' + ); + return; + } + + // case of one variable, one constant + + if (Field3.isConstant(x)) return assertLessThan(y_, x); + if (Field3.isConstant(y_)) { + y = typeof y === 'bigint' ? y : Field3.toBigint(y); + // this case is not included below, because ffadd doesn't support negative moduli + assert(y > 0n, 'assertLessThan: y <= 0, so x < y is impossible'); + + // we can just use negation `(y - 1) - x`. because the result is range-checked, it proves that x < y: + // `y - 1 - x \in [0, 2^3l) => x <= x + (y - 1 - x) = y - 1 < y` + // (note: ffadd can't add higher multiples of (f - 1). it must always use an overflow of -1, except for x = 0) + + ForeignField.negate(x, y - 1n); + return; + } + + // case of two variables + // we compute z = y - x - 1 and check that z \in [0, 2^3l), which implies x < y as above + + // we use modulo 0 here, which means we're proving: + // z = y - x - 1 - 0*(o1 + o2) for some overflows o1, o2 + sum([y_, x, Field3.from(1n)], [-1n, -1n], 0n); +} + +function assertLessThanOrEqual(x: Field3, y: bigint | Field3) { + assert( + typeof y !== 'bigint' || y >= 0n, + 'assertLessThanOrEqual: upper bound must be positive' + ); + let y_ = Field3.from(y); + + // constant case + if (Field3.isConstant(x) && Field3.isConstant(y_)) { + assert( + Field3.toBigint(x) <= Field3.toBigint(y_), + 'assertLessThan: got x > y' + ); + return; + } + + // provable case + // we compute z = y - x and check that z \in [0, 2^3l), which implies x <= y + sum([y_, x], [-1n], 0n); +} + +// helpers + +/** + * Version of `multiRangeCheck` which does the check on a truncated version of the input, + * so that it always succeeds, and then checks equality of the truncated and full input. + * + * This is a hack to get an error when the constraint fails, around the fact that multiRangeCheck + * is not checked by snarky. + */ +function indirectMultiRangeChange( + x: Field3, + message = 'multi-range check failed' +) { + let xTrunc = exists(3, () => { + let [x0, x1, x2] = toBigint3(x); + return [x0 & lMask, x1 & lMask, x2 & lMask]; + }); + multiRangeCheck(xTrunc); + x[0].assertEquals(xTrunc[0], message); + x[1].assertEquals(xTrunc[1], message); + x[2].assertEquals(xTrunc[2], message); +} diff --git a/src/lib/provable/gadgets/gadgets.ts b/src/lib/provable/gadgets/gadgets.ts new file mode 100644 index 0000000000..6a67973693 --- /dev/null +++ b/src/lib/provable/gadgets/gadgets.ts @@ -0,0 +1,840 @@ +/** + * Wrapper file for various gadgets, with a namespace and doccomments. + */ +import { + compactMultiRangeCheck, + multiRangeCheck, + rangeCheck16, + rangeCheck64, + rangeCheck32, + rangeCheckN, + isDefinitelyInRangeN, + rangeCheck8, +} from './range-check.js'; +import { + not, + rotate32, + rotate64, + xor, + and, + leftShift64, + rightShift64, + leftShift32, +} from './bitwise.js'; +import { Field } from '../wrapped.js'; +import { + ForeignField, + Field3, + Sum as ForeignFieldSum, +} from './foreign-field.js'; +import { divMod32, addMod32 } from './arithmetic.js'; +import { SHA256 } from './sha256.js'; + +export { Gadgets, Field3, ForeignFieldSum }; + +const Gadgets = { + /** + * Asserts that the input value is in the range [0, 2^64). + * + * This function proves that the provided field element can be represented with 64 bits. + * If the field element exceeds 64 bits, an error is thrown. + * + * @param x - The value to be range-checked. + * + * @throws Throws an error if the input value exceeds 64 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(12345678n)); + * Gadgets.rangeCheck64(x); // successfully proves 64-bit range + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * Gadgets.rangeCheck64(xLarge); // throws an error since input exceeds 64 bits + * ``` + * + * **Note**: Small "negative" field element inputs are interpreted as large integers close to the field size, + * and don't pass the 64-bit check. If you want to prove that a value lies in the int64 range [-2^63, 2^63), + * you could use `rangeCheck64(x.add(1n << 63n))`. + */ + rangeCheck64(x: Field) { + return rangeCheck64(x); + }, + + /** + * Asserts that the input value is in the range [0, 2^32). + * + * This function proves that the provided field element can be represented with 32 bits. + * If the field element exceeds 32 bits, an error is thrown. + * + * @param x - The value to be range-checked. + * + * @throws Throws an error if the input value exceeds 32 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(12345678n)); + * Gadgets.rangeCheck32(x); // successfully proves 32-bit range + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * Gadgets.rangeCheck32(xLarge); // throws an error since input exceeds 32 bits + * ``` + * + * **Note**: Small "negative" field element inputs are interpreted as large integers close to the field size, + * and don't pass the 32-bit check. If you want to prove that a value lies in the int32 range [-2^31, 2^31), + * you could use `rangeCheck32(x.add(1n << 31n))`. + */ + rangeCheck32(x: Field) { + return rangeCheck32(x); + }, + + /** + * Asserts that the input value is in the range [0, 2^n). `n` must be a multiple of 16. + * + * This function proves that the provided field element can be represented with `n` bits. + * If the field element exceeds `n` bits, an error is thrown. + * + * @param x - The value to be range-checked. + * @param n - The number of bits to be considered for the range check. + * @param message - Optional message to be displayed when the range check fails. + * + * @throws Throws an error if the input value exceeds `n` bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(12345678n)); + * Gadgets.rangeCheckN(32, x); // successfully proves 32-bit range + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * Gadgets.rangeCheckN(32, xLarge); // throws an error since input exceeds 32 bits + * ``` + */ + rangeCheckN(n: number, x: Field, message?: string) { + return rangeCheckN(n, x, message); + }, + + /** + * Returns a boolean which being true proves that x is in the range [0, 2^n). + * + * **Beware**: The output being false does **not** prove that x is not in the range [0, 2^n). + * This should not be viewed as a standalone provable method but as an advanced helper function + * for gadgets which need a weakened form of range check. + * + * @param x - The value to be weakly range-checked. + * @param n - The number of bits to be considered for the range check. + * + * @returns a Bool that is definitely only true if the input is in the range [0, 2^n), + * but could also be false _even if_ the input is in the range [0, 2^n). + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(12345678n)); + * let definitelyInRange = Gadgets.isDefinitelyInRangeN(32, x); // could be true or false + * ``` + */ + isDefinitelyInRangeN(n: number, x: Field) { + return isDefinitelyInRangeN(n, x); + }, + /* + * Asserts that the input value is in the range [0, 2^16). + * + * See {@link Gadgets.rangeCheck64} for analogous details and usage examples. + */ + rangeCheck16(x: Field) { + return rangeCheck16(x); + }, + + /** + * Asserts that the input value is in the range [0, 2^8). + * + * See {@link Gadgets.rangeCheck64} for analogous details and usage examples. + */ + rangeCheck8(x: Field) { + return rangeCheck8(x); + }, + + /** + * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, + * with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded. + * For a left rotation, this means that bits shifted off the left end reappear at the right end. + * Conversely, for a right rotation, bits shifted off the right end reappear at the left end. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * The `direction` parameter is a string that accepts either `'left'` or `'right'`, determining the direction of the rotation. + * + * **Important:** The gadget assumes that its input is at most 64 bits in size. + * + * If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution of the rotation. + * To safely use `rotate64()`, you need to make sure that the value passed in is range-checked to 64 bits; + * for example, using {@link Gadgets.rangeCheck64}. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#rotation) + * + * @param field {@link Field} element to rotate. + * @param bits amount of bits to rotate this {@link Field} element with. + * @param direction left or right rotation direction. + * + * @throws Throws an error if the input value exceeds 64 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(0b001100)); + * const y = Gadgets.rotate64(x, 2, 'left'); // left rotation by 2 bits + * const z = Gadgets.rotate64(x, 2, 'right'); // right rotation by 2 bits + * y.assertEquals(0b110000); + * z.assertEquals(0b000011); + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * Gadgets.rotate64(xLarge, 32, "left"); // throws an error since input exceeds 64 bits + * ``` + */ + rotate64(field: Field, bits: number, direction: 'left' | 'right' = 'left') { + return rotate64(field, bits, direction); + }, + /** + * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, + * with the distinction that the bits are circulated to the opposite end of a 32-bit representation rather than being discarded. + * For a left rotation, this means that bits shifted off the left end reappear at the right end. + * Conversely, for a right rotation, bits shifted off the right end reappear at the left end. + * + * It’s important to note that these operations are performed considering the big-endian 32-bit representation of the number, + * where the most significant (32th) bit is on the left end and the least significant bit is on the right end. + * The `direction` parameter is a string that accepts either `'left'` or `'right'`, determining the direction of the rotation. + * + * **Important:** The gadget assumes that its input is at most 32 bits in size. + * + * If the input exceeds 32 bits, the gadget is invalid and fails to prove correct execution of the rotation. + * To safely use `rotate32()`, you need to make sure that the value passed in is range-checked to 32 bits; + * for example, using {@link Gadgets.rangeCheck32}. + * + * + * @param field {@link Field} element to rotate. + * @param bits amount of bits to rotate this {@link Field} element with. + * @param direction left or right rotation direction. + * + * @throws Throws an error if the input value exceeds 32 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(0b001100)); + * const y = Gadgets.rotate32(x, 2, 'left'); // left rotation by 2 bits + * const z = Gadgets.rotate32(x, 2, 'right'); // right rotation by 2 bits + * y.assertEquals(0b110000); + * z.assertEquals(0b000011); + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * Gadgets.rotate32(xLarge, 32, "left"); // throws an error since input exceeds 32 bits + * ``` + */ + rotate32(field: Field, bits: number, direction: 'left' | 'right' = 'left') { + return rotate32(field, bits, direction); + }, + /** + * Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). + * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. + * + * This gadget builds a chain of XOR gates recursively. Each XOR gate can verify 16 bit at most. If your input elements exceed 16 bit, another XOR gate will be added to the chain. + * + * The `length` parameter lets you define how many bits should be compared. `length` is rounded to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well. + * + * **Note:** Specifying a larger `length` parameter adds additional constraints. + * + * It is also important to mention that specifying a smaller `length` allows the verifier to infer the length of the original input data (e.g. smaller than 16 bit if only one XOR gate has been used). + * A zkApp developer should consider these implications when choosing the `length` parameter and carefully weigh the trade-off between increased amount of constraints and security. + * + * **Important:** Both {@link Field} elements need to fit into `2^paddedLength - 1`. Otherwise, an error is thrown and no proof can be generated. + * + * For example, with `length = 2` (`paddedLength = 16`), `xor()` will fail for any input that is larger than `2**16`. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#xor-1) + * + * @param a {@link Field} element to compare. + * @param b {@link Field} element to compare. + * @param length amount of bits to compare. + * + * @throws Throws an error if the input values exceed `2^paddedLength - 1`. + * + * @example + * ```ts + * let a = Field(0b0101); + * let b = Field(0b0011); + * + * let c = Gadgets.xor(a, b, 4); // xor-ing 4 bits + * c.assertEquals(0b0110); + * ``` + */ + xor(a: Field, b: Field, length: number) { + return xor(a, b, length); + }, + + /** + * Bitwise NOT gate on {@link Field} elements. Similar to the [bitwise + * NOT `~` operator in JavaScript](https://developer.mozilla.org/en-US/docs/ + * Web/JavaScript/Reference/Operators/Bitwise_NOT). + * + * **Note:** The NOT gate only operates over the amount + * of bits specified by the `length` parameter. + * + * A NOT gate works by returning `1` in each bit position if the + * corresponding bit of the operand is `0`, and returning `0` if the + * corresponding bit of the operand is `1`. + * + * The `length` parameter lets you define how many bits to NOT. + * + * **Note:** Specifying a larger `length` parameter adds additional constraints. The operation will fail if the length or the input value is larger than 254. + * + * NOT is implemented in two different ways. If the `checked` parameter is set to `true` + * the {@link Gadgets.xor} gadget is reused with a second argument to be an + * all one bitmask the same length. This approach needs as many rows as an XOR would need + * for a single negation. If the `checked` parameter is set to `false`, NOT is + * implemented as a subtraction of the input from the all one bitmask. This + * implementation is returned by default if no `checked` parameter is provided. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#not) + * + * @example + * ```ts + * // not-ing 4 bits with the unchecked version + * let a = Field(0b0101); + * let b = Gadgets.not(a,4,false); + * + * b.assertEquals(0b1010); + * + * // not-ing 4 bits with the checked version utilizing the xor gadget + * let a = Field(0b0101); + * let b = Gadgets.not(a,4,true); + * + * b.assertEquals(0b1010); + * ``` + * + * @param a - The value to apply NOT to. The operation will fail if the value is larger than 254. + * @param length - The number of bits to be considered for the NOT operation. + * @param checked - Optional boolean to determine if the checked or unchecked not implementation is used. If it + * is set to `true` the {@link Gadgets.xor} gadget is reused. If it is set to `false`, NOT is implemented + * as a subtraction of the input from the all one bitmask. It is set to `false` by default if no parameter is provided. + * + * @throws Throws an error if the input value exceeds 254 bits. + */ + not(a: Field, length: number, checked: boolean = false) { + return not(a, length, checked); + }, + + /** + * Performs a left shift operation on the provided {@link Field} element. + * This operation is similar to the `<<` shift operation in JavaScript, + * where bits are shifted to the left, and the overflowing bits are discarded. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * + * **Important:** The gadgets assumes that its input is at most 64 bits in size. + * + * If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution of the shift. + * Therefore, to safely use `leftShift()`, you need to make sure that the values passed in are range checked to 64 bits. + * For example, this can be done with {@link Gadgets.rangeCheck64}. + * + * @param field {@link Field} element to shift. + * @param bits Amount of bits to shift the {@link Field} element to the left. The amount should be between 0 and 64 (or else the shift will fail). + * + * @throws Throws an error if the input value exceeds 64 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary + * const y = Gadgets.leftShift64(x, 2); // left shift by 2 bits + * y.assertEquals(0b110000); // 48 in binary + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * leftShift64(xLarge, 32); // throws an error since input exceeds 64 bits + * ``` + */ + leftShift64(field: Field, bits: number) { + return leftShift64(field, bits); + }, + + /** + * Performs a left shift operation on the provided {@link Field} element. + * This operation is similar to the `<<` shift operation in JavaScript, + * where bits are shifted to the left, and the overflowing bits are discarded. + * + * It’s important to note that these operations are performed considering the big-endian 32-bit representation of the number, + * where the most significant (32th) bit is on the left end and the least significant bit is on the right end. + * + * **Important:** The gadgets assumes that its input is at most 32 bits in size. + * + * The output is range checked to 32 bits. + * + * @param field {@link Field} element to shift. + * @param bits Amount of bits to shift the {@link Field} element to the left. The amount should be between 0 and 32 (or else the shift will fail). + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary + * const y = Gadgets.leftShift32(x, 2); // left shift by 2 bits + * y.assertEquals(0b110000); // 48 in binary + * ``` + */ + leftShift32(field: Field, bits: number) { + return leftShift32(field, bits); + }, + /** + * Performs a right shift operation on the provided {@link Field} element. + * This is similar to the `>>` shift operation in JavaScript, where bits are moved to the right. + * The `rightShift64` function utilizes the rotation method internally to implement this operation. + * + * * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * + * **Important:** The gadgets assumes that its input is at most 64 bits in size. + * + * If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution of the shift. + * To safely use `rightShift64()`, you need to make sure that the value passed in is range-checked to 64 bits; + * for example, using {@link Gadgets.rangeCheck64}. + * + * @param field {@link Field} element to shift. + * @param bits Amount of bits to shift the {@link Field} element to the right. The amount should be between 0 and 64 (or else the shift will fail). + * + * @throws Throws an error if the input value exceeds 64 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary + * const y = Gadgets.rightShift64(x, 2); // right shift by 2 bits + * y.assertEquals(0b000011); // 3 in binary + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * rightShift64(xLarge, 32); // throws an error since input exceeds 64 bits + * ``` + */ + rightShift64(field: Field, bits: number) { + return rightShift64(field, bits); + }, + /** + * Bitwise AND gadget on {@link Field} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND). + * The AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise. + * + * It can be checked by a double generic gate that verifies the following relationship between the values below (in the process it also invokes the {@link Gadgets.xor} gadget which will create additional constraints depending on `length`). + * + * The generic gate verifies:\ + * `a + b = sum` and the conjunction equation `2 * and = sum - xor`\ + * Where:\ + * `a + b = sum`\ + * `a ^ b = xor`\ + * `a & b = and` + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) + * + * The `length` parameter lets you define how many bits should be compared. `length` is rounded to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well. + * + * **Note:** Specifying a larger `length` parameter adds additional constraints. + * + * **Note:** Both {@link Field} elements need to fit into `2^paddedLength - 1`. Otherwise, an error is thrown and no proof can be generated. + * For example, with `length = 2` (`paddedLength = 16`), `and()` will fail for any input that is larger than `2**16`. + * + * @example + * ```typescript + * let a = Field(3); // ... 000011 + * let b = Field(5); // ... 000101 + * + * let c = Gadgets.and(a, b, 2); // ... 000001 + * c.assertEquals(1); + * ``` + */ + and(a: Field, b: Field, length: number) { + return and(a, b, length); + }, + + /** + * Multi-range check. + * + * Proves that x, y, z are all in the range [0, 2^88). + * + * This takes 4 rows, so it checks 88*3/4 = 66 bits per row. This is slightly more efficient + * than 64-bit range checks, which can do 64 bits in 1 row. + * + * In particular, the 3x88-bit range check supports bigints up to 264 bits, which in turn is enough + * to support foreign field multiplication with moduli up to 2^259. + * + * @example + * ```ts + * Gadgets.multiRangeCheck([x, y, z]); + * ``` + * + * @throws Throws an error if one of the input values exceeds 88 bits. + */ + multiRangeCheck(limbs: Field3) { + multiRangeCheck(limbs); + }, + + /** + * Compact multi-range check + * + * This is a variant of {@link multiRangeCheck} where the first two variables are passed in + * combined form xy = x + 2^88*y. + * + * The gadget + * - splits up xy into x and y + * - proves that xy = x + 2^88*y + * - proves that x, y, z are all in the range [0, 2^88). + * + * The split form [x, y, z] is returned. + * + * @example + * ```ts + * let [x, y] = Gadgets.compactMultiRangeCheck([xy, z]); + * ``` + * + * @throws Throws an error if `xy` exceeds 2*88 = 176 bits, or if z exceeds 88 bits. + */ + compactMultiRangeCheck(xy: Field, z: Field) { + return compactMultiRangeCheck(xy, z); + }, + + /** + * Gadgets for foreign field operations. + * + * A _foreign field_ is a finite field different from the native field of the proof system. + * + * The `ForeignField` namespace exposes operations like modular addition and multiplication, + * which work for any finite field of size less than 2^259. + * + * Foreign field elements are represented as 3 limbs of native field elements. + * Each limb holds 88 bits of the total, in little-endian order. + * + * All `ForeignField` gadgets expect that their input limbs are constrained to the range [0, 2^88). + * Range checks on outputs are added by the gadget itself. + */ + ForeignField: { + /** + * Foreign field addition: `x + y mod f` + * + * The modulus `f` does not need to be prime. + * + * Inputs and outputs are 3-tuples of native Fields. + * Each input limb is assumed to be in the range [0, 2^88), and the gadget is invalid if this is not the case. + * The result limbs are guaranteed to be in the same range. + * + * @example + * ```ts + * let x = Provable.witness(Field3.provable, () => Field3.from(9n)); + * let y = Provable.witness(Field3.provable, () => Field3.from(10n)); + * + * // range check x and y + * Gadgets.multiRangeCheck(x); + * Gadgets.multiRangeCheck(y); + * + * // compute x + y mod 17 + * let z = ForeignField.add(x, y, 17n); + * + * Provable.log(z); // ['2', '0', '0'] = limb representation of 2 = 9 + 10 mod 17 + * ``` + * + * **Warning**: The gadget does not assume that inputs are reduced modulo f, + * and does not prove that the result is reduced modulo f. + * It only guarantees that the result is in the correct residue class. + * + * @param x left summand + * @param y right summand + * @param f modulus + * @returns x + y mod f + */ + add(x: Field3, y: Field3, f: bigint) { + return ForeignField.add(x, y, f); + }, + + /** + * Foreign field subtraction: `x - y mod f` + * + * See {@link Gadgets.ForeignField.add} for assumptions and usage examples. + * + * @throws fails if `x - y < -f`, where the result cannot be brought back to a positive number by adding `f` once. + */ + sub(x: Field3, y: Field3, f: bigint) { + return ForeignField.sub(x, y, f); + }, + + /** + * Foreign field negation: `-x mod f = f - x` + * + * See {@link ForeignField.add} for assumptions and usage examples. + * + * @throws fails if `x > f`, where `f - x < 0`. + */ + neg(x: Field3, f: bigint) { + return ForeignField.negate(x, f); + }, + + /** + * Foreign field sum: `xs[0] + signs[0] * xs[1] + ... + signs[n-1] * xs[n] mod f` + * + * This gadget takes a list of inputs and a list of signs (of size one less than the inputs), + * and computes a chain of additions or subtractions, depending on the sign. + * A sign is of type `1n | -1n`, where `1n` represents addition and `-1n` represents subtraction. + * + * **Note**: For 3 or more inputs, `sum()` uses fewer constraints than a sequence of `add()` and `sub()` calls, + * because we can avoid range checks on intermediate results. + * + * See {@link Gadgets.ForeignField.add} for assumptions on inputs. + * + * @example + * ```ts + * let x = Provable.witness(Field3.provable, () => Field3.from(4n)); + * let y = Provable.witness(Field3.provable, () => Field3.from(5n)); + * let z = Provable.witness(Field3.provable, () => Field3.from(10n)); + * + * // range check x, y, z + * Gadgets.multiRangeCheck(x); + * Gadgets.multiRangeCheck(y); + * Gadgets.multiRangeCheck(z); + * + * // compute x + y - z mod 17 + * let sum = ForeignField.sum([x, y, z], [1n, -1n], 17n); + * + * Provable.log(sum); // ['16', '0', '0'] = limb representation of 16 = 4 + 5 - 10 mod 17 + * ``` + */ + sum(xs: Field3[], signs: (1n | -1n)[], f: bigint) { + return ForeignField.sum(xs, signs, f); + }, + + /** + * Foreign field multiplication: `x * y mod f` + * + * The modulus `f` does not need to be prime, but has to be smaller than 2^259. + * + * **Assumptions**: In addition to the assumption that input limbs are in the range [0, 2^88), as in all foreign field gadgets, + * this assumes an additional bound on the inputs: `x * y < 2^264 * p`, where p is the native modulus. + * We usually assert this bound by proving that `x[2] < f[2] + 1`, where `x[2]` is the most significant limb of x. + * To do this, we use an 88-bit range check on `2^88 - x[2] - (f[2] + 1)`, and same for y. + * The implication is that x and y are _almost_ reduced modulo f. + * + * All of the above assumptions are checked by {@link Gadgets.ForeignField.assertAlmostReduced}. + * + * **Warning**: This gadget does not add the extra bound check on the result. + * So, to use the result in another foreign field multiplication, you have to add the bound check on it yourself, again. + * + * @example + * ```ts + * // example modulus: secp256k1 prime + * let f = (1n << 256n) - (1n << 32n) - 0b1111010001n; + * + * let x = Provable.witness(Field3.provable, () => Field3.from(f - 1n)); + * let y = Provable.witness(Field3.provable, () => Field3.from(f - 2n)); + * + * // range check x, y and prove additional bounds x[2] <= f[2] + * ForeignField.assertAlmostReduced([x, y], f); + * + * // compute x * y mod f + * let z = ForeignField.mul(x, y, f); + * + * Provable.log(z); // ['2', '0', '0'] = limb representation of 2 = (-1)*(-2) mod f + * ``` + */ + mul(x: Field3, y: Field3, f: bigint) { + return ForeignField.mul(x, y, f); + }, + + /** + * Foreign field inverse: `x^(-1) mod f` + * + * See {@link Gadgets.ForeignField.mul} for assumptions on inputs and usage examples. + * + * This gadget adds an extra bound check on the result, so it can be used directly in another foreign field multiplication. + */ + inv(x: Field3, f: bigint) { + return ForeignField.inv(x, f); + }, + + /** + * Foreign field division: `x * y^(-1) mod f` + * + * See {@link Gadgets.ForeignField.mul} for assumptions on inputs and usage examples. + * + * This gadget adds an extra bound check on the result, so it can be used directly in another foreign field multiplication. + * + * @throws Different than {@link Gadgets.ForeignField.mul}, this fails on unreduced input `x`, because it checks that `x === (x/y)*y` and the right side will be reduced. + */ + div(x: Field3, y: Field3, f: bigint) { + return ForeignField.div(x, y, f); + }, + + /** + * Optimized multiplication of sums in a foreign field, for example: `(x - y)*z = a + b + c mod f` + * + * Note: This is much more efficient than using {@link Gadgets.ForeignField.add} and {@link Gadgets.ForeignField.sub} separately to + * compute the multiplication inputs and outputs, and then using {@link Gadgets.ForeignField.mul} to constrain the result. + * + * The sums passed into this method are "lazy sums" created with {@link Gadgets.ForeignField.Sum}. + * You can also pass in plain {@link Field3} elements. + * + * **Assumptions**: The assumptions on the _summands_ are analogous to the assumptions described in {@link Gadgets.ForeignField.mul}: + * - each summand's limbs are in the range [0, 2^88) + * - summands that are part of a multiplication input satisfy `x[2] <= f[2]` + * + * @throws if the modulus is so large that the second assumption no longer suffices for validity of the multiplication. + * For small sums and moduli < 2^256, this will not fail. + * + * @throws if the provided multiplication result is not correct modulo f. + * + * @example + * ```ts + * // range-check x, y, z, a, b, c + * ForeignField.assertAlmostReduced([x, y, z], f); + * Gadgets.multiRangeCheck(a); + * Gadgets.multiRangeCheck(b); + * Gadgets.multiRangeCheck(c); + * + * // create lazy input sums + * let xMinusY = ForeignField.Sum(x).sub(y); + * let aPlusBPlusC = ForeignField.Sum(a).add(b).add(c); + * + * // assert that (x - y)*z = a + b + c mod f + * ForeignField.assertMul(xMinusY, z, aPlusBPlusC, f); + * ``` + */ + assertMul( + x: Field3 | ForeignFieldSum, + y: Field3 | ForeignFieldSum, + z: Field3 | ForeignFieldSum, + f: bigint + ) { + return ForeignField.assertMul(x, y, z, f); + }, + + /** + * Lazy sum of {@link Field3} elements, which can be used as input to {@link Gadgets.ForeignField.assertMul}. + */ + Sum(x: Field3) { + return ForeignField.Sum(x); + }, + + /** + * Prove that each of the given {@link Field3} elements is "almost" reduced modulo f, + * i.e., satisfies the assumptions required by {@link Gadgets.ForeignField.mul} and other gadgets: + * - each limb is in the range [0, 2^88) + * - the most significant limb is less or equal than the modulus, x[2] <= f[2] + * + * **Note**: This method is most efficient when the number of input elements is a multiple of 3. + * + * @throws if any of the assumptions is violated. + * + * @example + * ```ts + * let x = Provable.witness(Field3.provable, () => Field3.from(4n)); + * let y = Provable.witness(Field3.provable, () => Field3.from(5n)); + * let z = Provable.witness(Field3.provable, () => Field3.from(10n)); + * + * ForeignField.assertAlmostReduced([x, y, z], f); + * + * // now we can use x, y, z as inputs to foreign field multiplication + * let xy = ForeignField.mul(x, y, f); + * let xyz = ForeignField.mul(xy, z, f); + * + * // since xy is an input to another multiplication, we need to prove that it is almost reduced again! + * ForeignField.assertAlmostReduced([xy], f); // TODO: would be more efficient to batch this with 2 other elements + * ``` + */ + assertAlmostReduced(xs: Field3[], f: bigint, { skipMrc = false } = {}) { + ForeignField.assertAlmostReduced(xs, f, skipMrc); + }, + + /** + * Prove that x < f for any constant f < 2^264, or for another `Field3` f. + * + * If f is a finite field modulus, this means that the given field element is fully reduced modulo f. + * This is a stronger statement than {@link ForeignField.assertAlmostReduced} + * and also uses more constraints; it should not be needed in most use cases. + * + * **Note**: This assumes that the limbs of x are in the range [0, 2^88), in contrast to + * {@link ForeignField.assertAlmostReduced} which adds that check itself. + * + * @throws if x is greater or equal to f. + * + * @example + * ```ts + * let x = Provable.witness(Field3.provable, () => Field3.from(0x1235n)); + * + * // range check limbs of x + * Gadgets.multiRangeCheck(x); + * + * // prove that x is fully reduced mod f + * Gadgets.ForeignField.assertLessThan(x, f); + * ``` + */ + assertLessThan(x: Field3, f: bigint | Field3) { + ForeignField.assertLessThan(x, f); + }, + + /** + * Prove that x <= f for any constant f < 2^264, or for another `Field3` f. + * + * See {@link ForeignField.assertLessThan} for details and usage examples. + */ + assertLessThanOrEqual(x: Field3, f: bigint | Field3) { + ForeignField.assertLessThanOrEqual(x, f); + }, + }, + + /** + * Helper methods to interact with 3-limb vectors of Fields. + * + * **Note:** This interface does not contain any provable methods. + */ + Field3, + /** + * Division modulo 2^32. The operation decomposes a {@link Field} element in the range [0, 2^64) into two 32-bit limbs, `remainder` and `quotient`, using the following equation: `n = quotient * 2^32 + remainder`. + * + * **Note:** The gadget acts as a proof that the input is in the range [0, 2^64). If the input exceeds 64 bits, the gadget fails. + * + * Asserts that both `remainder` and `quotient` are in the range [0, 2^32) using {@link Gadgets.rangeCheck32}. + * + * @example + * ```ts + * let n = Field((1n << 32n) + 8n) + * let { remainder, quotient } = Gadgets.divMod32(n); + * // remainder = 8, quotient = 1 + * + * n.assertEquals(quotient.mul(1n << 32n).add(remainder)); + * ``` + */ + divMod32, + + /** + * Addition modulo 2^32. The operation adds two {@link Field} elements in the range [0, 2^64] and returns the result modulo 2^32. + * + * Asserts that the result is in the range [0, 2^32) using {@link Gadgets.rangeCheck32}. + * + * It uses {@link Gadgets.divMod32} internally by adding the two {@link Field} elements and then decomposing the result into `remainder` and `quotient` and returning the `remainder`. + * + * **Note:** The gadget assumes both inputs to be in the range [0, 2^64). When called with non-range-checked inputs, be aware that the sum `a + b` can overflow the native field and the gadget can succeed but return an invalid result. + * + * @example + * ```ts + * let a = Field(8n); + * let b = Field(1n << 32n); + * + * Gadgets.addMod32(a, b).assertEquals(Field(8n)); + * ``` + * */ + addMod32, + + /** + * Implementation of the [SHA256 hash function.](https://en.wikipedia.org/wiki/SHA-2) Hash function with 256bit output. + * + * Applies the SHA2-256 hash function to a list of byte-sized elements. + * + * The function accepts {@link Bytes} as the input message, which is a type that represents a static-length list of byte-sized field elements (range-checked using {@link Gadgets.rangeCheck8}). + * Alternatively, you can pass plain `number[]`, `bigint[]` or `Uint8Array` to perform a hash outside provable code. + * + * Produces an output of {@link Bytes} that conforms to the chosen bit length. + * + * @param data - {@link Bytes} representing the message to hash. + * + * ```ts + * let preimage = Bytes.fromString("hello world"); + * let digest = Gadgets.SHA256.hash(preimage); + * ``` + * + */ + SHA256: SHA256, +}; diff --git a/src/lib/provable/gadgets/range-check.ts b/src/lib/provable/gadgets/range-check.ts new file mode 100644 index 0000000000..3ceb74bd93 --- /dev/null +++ b/src/lib/provable/gadgets/range-check.ts @@ -0,0 +1,343 @@ +import { Snarky } from '../../../snarky.js'; +import { Fp } from '../../../bindings/crypto/finite-field.js'; +import { BinableFp } from '../../../mina-signer/src/field-bigint.js'; +import type { Field } from '../field.js'; +import { Gates } from '../gates.js'; +import { assert, bitSlice, toVar, toVars } from './common.js'; +import { exists } from '../core/exists.js'; +import { createBool, createField } from '../core/field-constructor.js'; + +export { + rangeCheck64, + rangeCheck32, + multiRangeCheck, + compactMultiRangeCheck, + rangeCheckN, + isDefinitelyInRangeN, + rangeCheck8, + rangeCheck16, +}; +export { l, l2, l3, lMask, l2Mask }; + +/** + * Asserts that x is in the range [0, 2^32) + */ +function rangeCheck32(x: Field) { + if (x.isConstant()) { + if (x.toBigInt() >= 1n << 32n) { + throw Error(`rangeCheck32: expected field to fit in 32 bits, got ${x}`); + } + return; + } + + let actual = rangeCheckHelper(32, x); + actual.assertEquals(x); +} + +/** + * Asserts that x is in the range [0, 2^64) + */ +function rangeCheck64(x: Field) { + if (x.isConstant()) { + if (x.toBigInt() >= 1n << 64n) { + throw Error(`rangeCheck64: expected field to fit in 64 bits, got ${x}`); + } + return; + } + + // crumbs (2-bit limbs) + let [x0, x2, x4, x6, x8, x10, x12, x14] = exists(8, () => { + let xx = x.toBigInt(); + return [ + bitSlice(xx, 0, 2), + bitSlice(xx, 2, 2), + bitSlice(xx, 4, 2), + bitSlice(xx, 6, 2), + bitSlice(xx, 8, 2), + bitSlice(xx, 10, 2), + bitSlice(xx, 12, 2), + bitSlice(xx, 14, 2), + ]; + }); + + // 12-bit limbs + let [x16, x28, x40, x52] = exists(4, () => { + let xx = x.toBigInt(); + return [ + bitSlice(xx, 16, 12), + bitSlice(xx, 28, 12), + bitSlice(xx, 40, 12), + bitSlice(xx, 52, 12), + ]; + }); + + Gates.rangeCheck0( + x, + [createField(0), createField(0), x52, x40, x28, x16], + [x14, x12, x10, x8, x6, x4, x2, x0], + false // not using compact mode + ); +} + +// default bigint limb size +const l = 88n; +const l2 = 2n * l; +const l3 = 3n * l; +const lMask = (1n << l) - 1n; +const l2Mask = (1n << l2) - 1n; + +/** + * Asserts that x, y, z \in [0, 2^88) + */ +function multiRangeCheck([x, y, z]: [Field, Field, Field]) { + if (x.isConstant() && y.isConstant() && z.isConstant()) { + if (x.toBigInt() >> l || y.toBigInt() >> l || z.toBigInt() >> l) { + throw Error(`Expected fields to fit in ${l} bits, got ${x}, ${y}, ${z}`); + } + return; + } + // ensure we are using pure variables + [x, y, z] = toVars([x, y, z]); + let zero = toVar(0n); + + let [x64, x76] = rangeCheck0Helper(x); + let [y64, y76] = rangeCheck0Helper(y); + rangeCheck1Helper({ x64, x76, y64, y76, z, yz: zero }); +} + +/** + * Compact multi-range-check - checks + * - xy = x + 2^88*y + * - x, y, z \in [0, 2^88) + * + * Returns the full limbs x, y, z + */ +function compactMultiRangeCheck(xy: Field, z: Field): [Field, Field, Field] { + // constant case + if (xy.isConstant() && z.isConstant()) { + if (xy.toBigInt() >> l2 || z.toBigInt() >> l) { + throw Error( + `Expected fields to fit in ${l2} and ${l} bits respectively, got ${xy}, ${z}` + ); + } + let [x, y] = splitCompactLimb(xy.toBigInt()); + return [createField(x), createField(y), z]; + } + // ensure we are using pure variables + [xy, z] = toVars([xy, z]); + + let [x, y] = exists(2, () => splitCompactLimb(xy.toBigInt())); + + let [z64, z76] = rangeCheck0Helper(z, false); + let [x64, x76] = rangeCheck0Helper(x, true); + rangeCheck1Helper({ x64: z64, x76: z76, y64: x64, y76: x76, z: y, yz: xy }); + + return [x, y, z]; +} + +function splitCompactLimb(x01: bigint): [bigint, bigint] { + return [x01 & lMask, x01 >> l]; +} + +function rangeCheck0Helper(x: Field, isCompact = false): [Field, Field] { + // crumbs (2-bit limbs) + let [x0, x2, x4, x6, x8, x10, x12, x14] = exists(8, () => { + let xx = x.toBigInt(); + return [ + bitSlice(xx, 0, 2), + bitSlice(xx, 2, 2), + bitSlice(xx, 4, 2), + bitSlice(xx, 6, 2), + bitSlice(xx, 8, 2), + bitSlice(xx, 10, 2), + bitSlice(xx, 12, 2), + bitSlice(xx, 14, 2), + ]; + }); + + // 12-bit limbs + let [x16, x28, x40, x52, x64, x76] = exists(6, () => { + let xx = x.toBigInt(); + return [ + bitSlice(xx, 16, 12), + bitSlice(xx, 28, 12), + bitSlice(xx, 40, 12), + bitSlice(xx, 52, 12), + bitSlice(xx, 64, 12), + bitSlice(xx, 76, 12), + ]; + }); + + Gates.rangeCheck0( + x, + [x76, x64, x52, x40, x28, x16], + [x14, x12, x10, x8, x6, x4, x2, x0], + isCompact + ); + + // the two highest 12-bit limbs are returned because another gate + // is needed to add lookups for them + return [x64, x76]; +} + +function rangeCheck1Helper(inputs: { + x64: Field; + x76: Field; + y64: Field; + y76: Field; + z: Field; + yz: Field; +}) { + let { x64, x76, y64, y76, z, yz } = inputs; + + // create limbs for current row + let [z22, z24, z26, z28, z30, z32, z34, z36, z38, z50, z62, z74, z86] = + exists(13, () => { + let zz = z.toBigInt(); + return [ + bitSlice(zz, 22, 2), + bitSlice(zz, 24, 2), + bitSlice(zz, 26, 2), + bitSlice(zz, 28, 2), + bitSlice(zz, 30, 2), + bitSlice(zz, 32, 2), + bitSlice(zz, 34, 2), + bitSlice(zz, 36, 2), + bitSlice(zz, 38, 12), + bitSlice(zz, 50, 12), + bitSlice(zz, 62, 12), + bitSlice(zz, 74, 12), + bitSlice(zz, 86, 2), + ]; + }); + + // create limbs for next row + let [z0, z2, z4, z6, z8, z10, z12, z14, z16, z18, z20] = exists(11, () => { + let zz = z.toBigInt(); + return [ + bitSlice(zz, 0, 2), + bitSlice(zz, 2, 2), + bitSlice(zz, 4, 2), + bitSlice(zz, 6, 2), + bitSlice(zz, 8, 2), + bitSlice(zz, 10, 2), + bitSlice(zz, 12, 2), + bitSlice(zz, 14, 2), + bitSlice(zz, 16, 2), + bitSlice(zz, 18, 2), + bitSlice(zz, 20, 2), + ]; + }); + + Gates.rangeCheck1( + z, + yz, + [z86, z74, z62, z50, z38, z36, z34, z32, z30, z28, z26, z24, z22], + [z20, z18, z16, x76, x64, y76, y64, z14, z12, z10, z8, z6, z4, z2, z0] + ); +} + +/** + * Helper function that creates a new {@link Field} element from the first `length` bits of this {@link Field} element. + * + * This returns the `x` truncated to `length` bits. However, it does **not** prove this truncation or any + * other relation of the output with `x`. + * + * This only proves that the output value is in the range [0, 2^length), and so can be combined + * with the initial value to prove a range check. + */ +function rangeCheckHelper(length: number, x: Field) { + assert( + length <= Fp.sizeInBits, + `bit length must be ${Fp.sizeInBits} or less, got ${length}` + ); + assert(length > 0, `bit length must be positive, got ${length}`); + assert(length % 16 === 0, '`length` has to be a multiple of 16.'); + + let lengthDiv16 = length / 16; + if (x.isConstant()) { + let bits = BinableFp.toBits(x.toBigInt()) + .slice(0, length) + .concat(Array(Fp.sizeInBits - length).fill(false)); + return createField(BinableFp.fromBits(bits)); + } + let y = Snarky.field.truncateToBits16(lengthDiv16, x.value); + return createField(y); +} + +/** + * Asserts that x is in the range [0, 2^n) + */ +function rangeCheckN(n: number, x: Field, message: string = '') { + assert( + n <= Fp.sizeInBits, + `bit length must be ${Fp.sizeInBits} or less, got ${n}` + ); + assert(n > 0, `bit length must be positive, got ${n}`); + assert(n % 16 === 0, '`length` has to be a multiple of 16.'); + + if (x.isConstant()) { + if (x.toBigInt() >= 1n << BigInt(n)) { + throw Error( + `rangeCheckN: expected field to fit in ${n} bits, got ${x}.\n${message}` + ); + } + return; + } + + let actual = rangeCheckHelper(n, x); + actual.assertEquals(x, message); +} + +/** + * Returns a boolean which, being true, proves that x is in the range [0, 2^n). + * + * **Beware**: The output being false does **not** prove that x is not in the range [0, 2^n). + * In other words, it can happen that this returns false even if x is in the range [0, 2^n). + * + * This should not be viewed as a standalone provable method but as an advanced helper function + * for gadgets which need a weakened form of range check. + */ +function isDefinitelyInRangeN(n: number, x: Field) { + assert( + n <= Fp.sizeInBits, + `bit length must be ${Fp.sizeInBits} or less, got ${n}` + ); + assert(n > 0, `bit length must be positive, got ${n}`); + assert(n % 16 === 0, '`length` has to be a multiple of 16.'); + + if (x.isConstant()) { + return createBool(x.toBigInt() < 1n << BigInt(n)); + } + + let actual = rangeCheckHelper(n, x); + return actual.equals(x); +} + +function rangeCheck16(x: Field) { + if (x.isConstant()) { + assert( + x.toBigInt() < 1n << 16n, + `rangeCheck16: expected field to fit in 8 bits, got ${x}` + ); + return; + } + // check that x fits in 16 bits + rangeCheckHelper(16, x).assertEquals(x); +} + +function rangeCheck8(x: Field) { + if (x.isConstant()) { + assert( + x.toBigInt() < 1n << 8n, + `rangeCheck8: expected field to fit in 8 bits, got ${x}` + ); + return; + } + + // check that x fits in 16 bits + rangeCheckHelper(16, x).assertEquals(x); + // check that 2^8 x fits in 16 bits + let x256 = x.mul(1 << 8).seal(); + rangeCheckHelper(16, x256).assertEquals(x256); +} diff --git a/src/lib/provable/gadgets/sha256.ts b/src/lib/provable/gadgets/sha256.ts new file mode 100644 index 0000000000..38a5c82b14 --- /dev/null +++ b/src/lib/provable/gadgets/sha256.ts @@ -0,0 +1,289 @@ +// https://csrc.nist.gov/pubs/fips/180-4/upd1/final +import { mod } from '../../../bindings/crypto/finite-field.js'; +import { Field } from '../wrapped.js'; +import { UInt32, UInt8 } from '../int.js'; +import { exists } from '../core/exists.js'; +import { FlexibleBytes } from '../bytes.js'; +import { Bytes } from '../wrapped-classes.js'; +import { chunk } from '../../util/arrays.js'; +import { TupleN } from '../../util/types.js'; +import { divMod32 } from './arithmetic.js'; +import { bytesToWord, wordToBytes } from './bit-slices.js'; +import { bitSlice } from './common.js'; +import { rangeCheck16 } from './range-check.js'; + +export { SHA256 }; + +const SHA256Constants = { + // constants §4.2.2 + K: [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + ], + // initial hash values §5.3.3 + H: [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, + 0x1f83d9ab, 0x5be0cd19, + ], +}; + +function padding(data: FlexibleBytes): UInt32[][] { + // create a provable Bytes instance from the input data + // the Bytes class will be static sized according to the length of the input data + let message = Bytes.from(data); + + // now pad the data to reach the format expected by sha256 + // pad 1 bit, followed by k zero bits where k is the smallest non-negative solution to + // l + 1 + k = 448 mod 512 + // then append a 64bit block containing the length of the original message in bits + + let l = message.length * 8; // length in bits + let k = Number(mod(448n - (BigInt(l) + 1n), 512n)); + + let lBinary = l.toString(2); + + let paddingBits = ( + '1' + // append 1 bit + '0'.repeat(k) + // append k zero bits + '0'.repeat(64 - lBinary.length) + // append 64bit containing the length of the original message + lBinary + ).match(/.{1,8}/g)!; // this should always be divisible by 8 + + // map the padding bit string to UInt8 elements + let padding = paddingBits.map((x) => UInt8.from(BigInt('0b' + x))); + + // concatenate the padding with the original padded data + let paddedMessage = message.bytes.concat(padding); + + // split the message into 32bit chunks + let chunks: UInt32[] = []; + + for (let i = 0; i < paddedMessage.length; i += 4) { + // chunk 4 bytes into one UInt32, as expected by SHA256 + // bytesToWord expects little endian, so we reverse the bytes + chunks.push( + UInt32.Unsafe.fromField( + bytesToWord(paddedMessage.slice(i, i + 4).reverse()) + ) + ); + } + + // split message into 16 element sized message blocks + // SHA256 expects n-blocks of 512bit each, 16*32bit = 512bit + return chunk(chunks, 16); +} + +const SHA256 = { + hash(data: FlexibleBytes) { + // preprocessing §6.2 + // padding the message $5.1.1 into blocks that are a multiple of 512 + let messageBlocks = padding(data); + + const H = SHA256Constants.H.map((x) => UInt32.from(x)); + const K = SHA256Constants.K.map((x) => UInt32.from(x)); + + const N = messageBlocks.length; + + for (let i = 0; i < N; i++) { + const M = messageBlocks[i]; + // for each message block of 16 x 32bit do: + const W: UInt32[] = []; + + // prepare message block + for (let t = 0; t <= 15; t++) W[t] = M[t]; + for (let t = 16; t <= 63; t++) { + // the field element is unreduced and not proven to be 32bit, we will do this later to save constraints + let unreduced = DeltaOne(W[t - 2]) + .value.add(W[t - 7].value) + .add(DeltaZero(W[t - 15]).value.add(W[t - 16].value)); + + // mod 32bit the unreduced field element + W[t] = UInt32.Unsafe.fromField(divMod32(unreduced, 16).remainder); + } + + // initialize working variables + let a = H[0]; + let b = H[1]; + let c = H[2]; + let d = H[3]; + let e = H[4]; + let f = H[5]; + let g = H[6]; + let h = H[7]; + + // main loop + for (let t = 0; t <= 63; t++) { + // T1 is unreduced and not proven to be 32bit, we will do this later to save constraints + const unreducedT1 = h.value + .add(SigmaOne(e).value) + .add(Ch(e, f, g).value) + .add(K[t].value) + .add(W[t].value) + .seal(); + + // T2 is also unreduced + const unreducedT2 = SigmaZero(a).value.add(Maj(a, b, c).value); + + h = g; + g = f; + f = e; + e = UInt32.Unsafe.fromField( + divMod32(d.value.add(unreducedT1), 16).remainder + ); // mod 32bit the unreduced field element + d = c; + c = b; + b = a; + a = UInt32.Unsafe.fromField( + divMod32(unreducedT2.add(unreducedT1), 16).remainder + ); // mod 32bit + } + + // new intermediate hash value + H[0] = H[0].addMod32(a); + H[1] = H[1].addMod32(b); + H[2] = H[2].addMod32(c); + H[3] = H[3].addMod32(d); + H[4] = H[4].addMod32(e); + H[5] = H[5].addMod32(f); + H[6] = H[6].addMod32(g); + H[7] = H[7].addMod32(h); + } + + // the working variables H[i] are 32bit, however we want to decompose them into bytes to be more compatible + // wordToBytes expects little endian, so we reverse the bytes + return Bytes.from(H.map((x) => wordToBytes(x.value, 4).reverse()).flat()); + }, +}; + +function Ch(x: UInt32, y: UInt32, z: UInt32) { + // ch(x, y, z) = (x & y) ^ (~x & z) + // = (x & y) + (~x & z) (since x & ~x = 0) + let xAndY = x.and(y).value; + let xNotAndZ = x.not().and(z).value; + let ch = xAndY.add(xNotAndZ).seal(); + return UInt32.Unsafe.fromField(ch); +} + +function Maj(x: UInt32, y: UInt32, z: UInt32) { + // maj(x, y, z) = (x & y) ^ (x & z) ^ (y & z) + // = (x + y + z - (x ^ y ^ z)) / 2 + let sum = x.value.add(y.value).add(z.value).seal(); + let xor = x.xor(y).xor(z).value; + let maj = sum.sub(xor).div(2).seal(); + return UInt32.Unsafe.fromField(maj); +} + +function SigmaZero(x: UInt32) { + return sigma(x, [2, 13, 22]); +} + +function SigmaOne(x: UInt32) { + return sigma(x, [6, 11, 25]); +} + +// lowercase sigma = delta to avoid confusing function names + +function DeltaZero(x: UInt32) { + return sigma(x, [3, 7, 18], true); +} + +function DeltaOne(x: UInt32) { + return sigma(x, [10, 17, 19], true); +} + +function ROTR(n: number, x: UInt32) { + return x.rotate(n, 'right'); +} + +function SHR(n: number, x: UInt32) { + let val = x.rightShift(n); + return val; +} + +function sigmaSimple(u: UInt32, bits: TupleN, firstShifted = false) { + let [r0, r1, r2] = bits; + let rot0 = firstShifted ? SHR(r0, u) : ROTR(r0, u); + let rot1 = ROTR(r1, u); + let rot2 = ROTR(r2, u); + return rot0.xor(rot1).xor(rot2); +} + +function sigma(u: UInt32, bits: TupleN, firstShifted = false) { + if (u.isConstant()) return sigmaSimple(u, bits, firstShifted); + + let [r0, r1, r2] = bits; // TODO assert bits are sorted + let x = u.value; + + let d0 = r0; + let d1 = r1 - r0; + let d2 = r2 - r1; + let d3 = 32 - r2; + + // decompose x into 4 chunks of size d0, d1, d2, d3 + let [x0, x1, x2, x3] = exists(4, () => { + let xx = x.toBigInt(); + return [ + bitSlice(xx, 0, d0), + bitSlice(xx, r0, d1), + bitSlice(xx, r1, d2), + bitSlice(xx, r2, d3), + ]; + }); + + // range check each chunk + // we only need to range check to 16 bits relying on the requirement that + // the rotated values are range-checked to 32 bits later; see comments below + rangeCheck16(x0); + rangeCheck16(x1); + rangeCheck16(x2); + rangeCheck16(x3); + + // prove x decomposition + + // x === x0 + x1*2^d0 + x2*2^(d0+d1) + x3*2^(d0+d1+d2) + let x23 = x2.add(x3.mul(1 << d2)).seal(); + let x123 = x1.add(x23.mul(1 << d1)).seal(); + x0.add(x123.mul(1 << d0)).assertEquals(x); + // ^ proves that 2^(32-d3)*x3 < x < 2^32 => x3 < 2^d3 + + // reassemble chunks into rotated values + + let xRotR0: Field; + + if (!firstShifted) { + // rotr(x, r0) = x1 + x2*2^d1 + x3*2^(d1+d2) + x0*2^(d1+d2+d3) + xRotR0 = x123.add(x0.mul(1 << (d1 + d2 + d3))).seal(); + // ^ proves that 2^(32-d0)*x0 < xRotR0 => x0 < 2^d0 if we check xRotR0 < 2^32 later + } else { + // shr(x, r0) = x1 + x2*2^d1 + x3*2^(d1+d2) + xRotR0 = x123; + + // finish x0 < 2^d0 proof: + rangeCheck16(x0.mul(1 << (16 - d0)).seal()); + } + + // rotr(x, r1) = x2 + x3*2^d2 + x0*2^(d2+d3) + x1*2^(d2+d3+d0) + let x01 = x0.add(x1.mul(1 << d0)).seal(); + let xRotR1 = x23.add(x01.mul(1 << (d2 + d3))).seal(); + // ^ proves that 2^(32-d1)*x1 < xRotR1 => x1 < 2^d1 if we check xRotR1 < 2^32 later + + // rotr(x, r2) = x3 + x0*2^d3 + x1*2^(d3+d0) + x2*2^(d3+d0+d1) + let x012 = x01.add(x2.mul(1 << (d0 + d1))).seal(); + let xRotR2 = x3.add(x012.mul(1 << d3)).seal(); + // ^ proves that 2^(32-d2)*x2 < xRotR2 => x2 < 2^d2 if we check xRotR2 < 2^32 later + + // since xor() is implicitly range-checking both of its inputs, this provides the missing + // proof that xRotR0, xRotR1, xRotR2 < 2^32, which implies x0 < 2^d0, x1 < 2^d1, x2 < 2^d2 + return UInt32.Unsafe.fromField(xRotR0) + .xor(UInt32.Unsafe.fromField(xRotR1)) + .xor(UInt32.Unsafe.fromField(xRotR2)); +} diff --git a/src/lib/provable/gates.ts b/src/lib/provable/gates.ts new file mode 100644 index 0000000000..ed25ea1bf5 --- /dev/null +++ b/src/lib/provable/gates.ts @@ -0,0 +1,284 @@ +import { Snarky } from '../../snarky.js'; +import type { Field } from './field.js'; +import { FieldVar, FieldConst } from './core/fieldvar.js'; +import { MlArray, MlTuple } from '../ml/base.js'; +import { exists } from './core/exists.js'; +import { TupleN } from '../util/types.js'; + +export { + Gates, + rangeCheck0, + rangeCheck1, + xor, + zero, + rotate, + generic, + foreignFieldAdd, + foreignFieldMul, + KimchiGateType, +}; + +export { fieldVar }; + +const Gates = { + rangeCheck0, + rangeCheck1, + xor, + zero, + rotate, + generic, + foreignFieldAdd, + foreignFieldMul, + raw, +}; + +function rangeCheck0( + x: Field, + xLimbs12: TupleN, + xLimbs2: TupleN, + isCompact: boolean +) { + Snarky.gates.rangeCheck0( + x.value, + MlTuple.mapTo(xLimbs12, (x) => x.value), + MlTuple.mapTo(xLimbs2, (x) => x.value), + isCompact ? FieldConst[1] : FieldConst[0] + ); +} + +/** + * the rangeCheck1 gate is used in combination with the rangeCheck0, + * for doing a 3x88-bit range check + */ +function rangeCheck1( + v2: Field, + v12: Field, + vCurr: TupleN, + vNext: TupleN +) { + Snarky.gates.rangeCheck1( + v2.value, + v12.value, + MlTuple.mapTo(vCurr, (x) => x.value), + MlTuple.mapTo(vNext, (x) => x.value) + ); +} + +function rotate( + field: Field, + rotated: Field, + excess: Field, + limbs: [Field, Field, Field, Field], + crumbs: [Field, Field, Field, Field, Field, Field, Field, Field], + two_to_rot: bigint +) { + Snarky.gates.rotate( + field.value, + rotated.value, + excess.value, + MlArray.to(limbs.map((x) => x.value)), + MlArray.to(crumbs.map((x) => x.value)), + FieldConst.fromBigint(two_to_rot) + ); +} + +/** + * Asserts that 16 bit limbs of input two elements are the correct XOR output + */ +function xor( + input1: Field, + input2: Field, + outputXor: Field, + in1_0: Field, + in1_1: Field, + in1_2: Field, + in1_3: Field, + in2_0: Field, + in2_1: Field, + in2_2: Field, + in2_3: Field, + out0: Field, + out1: Field, + out2: Field, + out3: Field +) { + Snarky.gates.xor( + input1.value, + input2.value, + outputXor.value, + in1_0.value, + in1_1.value, + in1_2.value, + in1_3.value, + in2_0.value, + in2_1.value, + in2_2.value, + in2_3.value, + out0.value, + out1.value, + out2.value, + out3.value + ); +} + +/** + * [Generic gate](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=foreignfield#double-generic-gate) + * The vanilla PLONK gate that allows us to do operations like: + * * addition of two registers (into an output register) + * * multiplication of two registers + * * equality of a register with a constant + * + * More generally, the generic gate controls the coefficients (denoted `c_`) in the equation: + * + * `c_l*l + c_r*r + c_o*o + c_m*l*r + c_c === 0` + */ +function generic( + coefficients: { + left: bigint | FieldConst; + right: bigint | FieldConst; + out: bigint | FieldConst; + mul: bigint | FieldConst; + const: bigint | FieldConst; + }, + inputs: { + left: Field | FieldVar; + right: Field | FieldVar; + out: Field | FieldVar; + } +) { + Snarky.gates.generic( + fieldConst(coefficients.left), + fieldVar(inputs.left), + fieldConst(coefficients.right), + fieldVar(inputs.right), + fieldConst(coefficients.out), + fieldVar(inputs.out), + fieldConst(coefficients.mul), + fieldConst(coefficients.const) + ); +} + +function zero(a: Field, b: Field, c: Field) { + raw(KimchiGateType.Zero, [a, b, c], []); +} + +/** + * bigint addition which allows for field overflow and carry + * + * - `l01 + sign*r01 - overflow*f01 - carry*2^2l === r01` + * - `l2 + sign*r2 - overflow*f2 + carry === r2` + * - overflow is 0 or sign + * - carry is 0, 1 or -1 + * + * assumes that the result is placed in the first 3 cells of the next row! + */ +function foreignFieldAdd({ + left, + right, + overflow, + carry, + modulus, + sign, +}: { + left: TupleN; + right: TupleN; + overflow: Field; + carry: Field; + modulus: TupleN; + sign: 1n | -1n; +}) { + Snarky.gates.foreignFieldAdd( + MlTuple.mapTo(left, (x) => x.value), + MlTuple.mapTo(right, (x) => x.value), + overflow.value, + carry.value, + MlTuple.mapTo(modulus, FieldConst.fromBigint), + FieldConst.fromBigint(sign) + ); +} + +/** + * Foreign field multiplication + */ +function foreignFieldMul(inputs: { + left: TupleN; + right: TupleN; + remainder: TupleN; + quotient: TupleN; + quotientHiBound: Field; + product1: TupleN; + carry0: Field; + carry1p: TupleN; + carry1c: TupleN; + foreignFieldModulus2: bigint; + negForeignFieldModulus: TupleN; +}) { + let { + left, + right, + remainder, + quotient, + quotientHiBound, + product1, + carry0, + carry1p, + carry1c, + foreignFieldModulus2, + negForeignFieldModulus, + } = inputs; + + Snarky.gates.foreignFieldMul( + MlTuple.mapTo(left, (x) => x.value), + MlTuple.mapTo(right, (x) => x.value), + MlTuple.mapTo(remainder, (x) => x.value), + MlTuple.mapTo(quotient, (x) => x.value), + quotientHiBound.value, + MlTuple.mapTo(product1, (x) => x.value), + carry0.value, + MlTuple.mapTo(carry1p, (x) => x.value), + MlTuple.mapTo(carry1c, (x) => x.value), + FieldConst.fromBigint(foreignFieldModulus2), + MlTuple.mapTo(negForeignFieldModulus, FieldConst.fromBigint) + ); +} + +function raw(kind: KimchiGateType, values: Field[], coefficients: bigint[]) { + let n = values.length; + let padding = exists(15 - n, () => Array(15 - n).fill(0n)); + Snarky.gates.raw( + kind, + MlArray.to(values.concat(padding).map((x) => x.value)), + MlArray.to(coefficients.map(FieldConst.fromBigint)) + ); +} + +enum KimchiGateType { + Zero, + Generic, + Poseidon, + CompleteAdd, + VarBaseMul, + EndoMul, + EndoMulScalar, + Lookup, + CairoClaim, + CairoInstruction, + CairoFlags, + CairoTransition, + RangeCheck0, + RangeCheck1, + ForeignFieldAdd, + ForeignFieldMul, + Xor16, + Rot64, +} + +// helper + +function fieldVar(x: Field | FieldVar | bigint): FieldVar { + if (typeof x === 'bigint') return FieldVar.constant(x); + return Array.isArray(x) ? x : x.value; +} +function fieldConst(x: bigint | FieldConst): FieldConst { + return typeof x === 'bigint' ? FieldConst.fromBigint(x) : x; +} diff --git a/src/lib/group.ts b/src/lib/provable/group.ts similarity index 67% rename from src/lib/group.ts rename to src/lib/provable/group.ts index 71f95fdae8..31cee90cfd 100644 --- a/src/lib/group.ts +++ b/src/lib/provable/group.ts @@ -1,8 +1,9 @@ -import { Field, FieldVar, isField } from './field.js'; +import { Field } from './field.js'; +import { FieldVar } from './core/fieldvar.js'; import { Scalar } from './scalar.js'; -import { Snarky } from '../snarky.js'; -import { Field as Fp } from '../provable/field-bigint.js'; -import { Pallas } from '../bindings/crypto/elliptic_curve.js'; +import { Snarky } from '../../snarky.js'; +import { Fp } from '../../bindings/crypto/finite-field.js'; +import { GroupAffine, Pallas } from '../../bindings/crypto/elliptic-curve.js'; import { Provable } from './provable.js'; import { Bool } from './bool.js'; @@ -35,10 +36,7 @@ class Group { * ``` */ static get zero() { - return new Group({ - x: 0, - y: 0, - }); + return new Group({ x: 0, y: 0 }); } /** @@ -51,10 +49,10 @@ class Group { x: FieldVar | Field | number | string | bigint; y: FieldVar | Field | number | string | bigint; }) { - this.x = isField(x) ? x : new Field(x); - this.y = isField(y) ? y : new Field(y); + this.x = x instanceof Field ? x : new Field(x); + this.y = y instanceof Field ? y : new Field(y); - if (this.#isConstant()) { + if (isConstant(this)) { // we also check the zero element (0, 0) here if (this.x.equals(0).and(this.y.equals(0)).toBoolean()) return; @@ -75,39 +73,6 @@ class Group { } } - // helpers - static #fromAffine({ - x, - y, - infinity, - }: { - x: bigint; - y: bigint; - infinity: boolean; - }) { - return infinity ? Group.zero : new Group({ x, y }); - } - - static #fromProjective({ x, y, z }: { x: bigint; y: bigint; z: bigint }) { - return this.#fromAffine(Pallas.toAffine({ x, y, z })); - } - - #toTuple(): [0, FieldVar, FieldVar] { - return [0, this.x.value, this.y.value]; - } - - #isConstant() { - return this.x.isConstant() && this.y.isConstant(); - } - - #toProjective() { - return Pallas.fromAffine({ - x: this.x.toBigInt(), - y: this.y.toBigInt(), - infinity: false, - }); - } - /** * Checks if this element is the `zero` element `{x: 0, y: 0}`. */ @@ -125,15 +90,15 @@ class Group { * ``` */ add(g: Group) { - if (this.#isConstant() && g.#isConstant()) { + if (isConstant(this) && isConstant(g)) { // we check if either operand is zero, because adding zero to g just results in g (and vise versa) if (this.isZero().toBoolean()) { return g; } else if (g.isZero().toBoolean()) { return this; } else { - let g_proj = Pallas.add(this.#toProjective(), g.#toProjective()); - return Group.#fromProjective(g_proj); + let g_proj = Pallas.add(toProjective(this), toProjective(g)); + return fromProjective(g_proj); } } else { const { x: x1, y: y1 } = this; @@ -173,10 +138,10 @@ class Group { return s.mul(x1.sub(x3)).sub(y1); }); - let [, x, y] = Snarky.group.ecadd( - Group.from(x1.seal(), y1.seal()).#toTuple(), - Group.from(x2.seal(), y2.seal()).#toTuple(), - Group.from(x3, y3).#toTuple(), + let [, x, y] = Snarky.gates.ecAdd( + toTuple(Group.from(x1.seal(), y1.seal())), + toTuple(Group.from(x2.seal(), y2.seal())), + toTuple(Group.from(x3, y3)), inf.toField().value, same_x.value, s.value, @@ -187,27 +152,15 @@ class Group { // similarly to the constant implementation, we check if either operand is zero // and the implementation above (original OCaml implementation) returns something wild -> g + 0 != g where it should be g + 0 = g let gIsZero = g.isZero(); - let thisIsZero = this.isZero(); - - let bothZero = gIsZero.and(thisIsZero); - - let onlyGisZero = gIsZero.and(thisIsZero.not()); - let onlyThisIsZero = thisIsZero.and(gIsZero.not()); - + let onlyThisIsZero = this.isZero().and(gIsZero.not()); let isNegation = inf; + let isNormalAddition = gIsZero.or(onlyThisIsZero).or(isNegation).not(); - let isNewElement = bothZero - .not() - .and(isNegation.not()) - .and(onlyThisIsZero.not()) - .and(onlyGisZero.not()); - - const zero_g = Group.zero; - + // note: gIsZero and isNegation are not mutually exclusive, but if both are true, we add 1*0 + 1*0 = 0 which is correct return Provable.switch( - [bothZero, onlyGisZero, onlyThisIsZero, isNegation, isNewElement], + [gIsZero, onlyThisIsZero, isNegation, isNormalAddition], Group, - [zero_g, this, g, zero_g, new Group({ x, y })] + [this, g, Group.zero, new Group({ x, y })] ); } } @@ -239,13 +192,13 @@ class Group { scale(s: Scalar | number | bigint) { let scalar = Scalar.from(s); - if (this.#isConstant() && scalar.isConstant()) { - let g_proj = Pallas.scale(this.#toProjective(), scalar.toBigInt()); - return Group.#fromProjective(g_proj); + if (isConstant(this) && scalar.isConstant()) { + let g_proj = Pallas.scale(toProjective(this), scalar.toBigInt()); + return fromProjective(g_proj); } else { let [, ...bits] = scalar.value; bits.reverse(); - let [, x, y] = Snarky.group.scale(this.#toTuple(), [0, ...bits]); + let [, x, y] = Snarky.group.scale(toTuple(this), [0, ...bits]); return new Group({ x, y }); } } @@ -315,79 +268,6 @@ class Group { return new Group({ x, y }); } - /** - * @deprecated Please use the method `.add` on the instance instead - * - * Adds a {@link Group} element to another one. - */ - static add(g1: Group, g2: Group) { - return g1.add(g2); - } - - /** - * @deprecated Please use the method `.sub` on the instance instead - * - * Subtracts a {@link Group} element from another one. - */ - static sub(g1: Group, g2: Group) { - return g1.sub(g2); - } - - /** - * @deprecated Please use the method `.neg` on the instance instead - * - * Negates a {@link Group} element. Under the hood, it simply negates the `y` coordinate and leaves the `x` coordinate as is. - * - * ```typescript - * let gNeg = Group.neg(g); - * ``` - */ - static neg(g: Group) { - return g.neg(); - } - - /** - * @deprecated Please use the method `.scale` on the instance instead - * - * Elliptic curve scalar multiplication. Scales a {@link Group} element `n`-times by itself, where `n` is the {@link Scalar}. - * - * ```typescript - * let s = Scalar(5); - * let 5g = Group.scale(g, s); - * ``` - */ - static scale(g: Group, s: Scalar) { - return g.scale(s); - } - - /** - * @deprecated Please use the method `.assertEqual` on the instance instead. - * - * Assert that two {@link Group} elements are equal to another. - * Throws an error if the assertion fails. - * - * ```ts - * Group.assertEquals(g1, g2); - * ``` - */ - static assertEqual(g1: Group, g2: Group) { - g1.assertEquals(g2); - } - - /** - * @deprecated Please use the method `.equals` on the instance instead. - * - * Checks if a {@link Group} element is equal to another {@link Group} element. - * Returns a {@link Bool}. - * - * ```ts - * Group.equal(g1, g2); // Bool(true) - * ``` - */ - static equal(g1: Group, g2: Group) { - return g1.equals(g2); - } - /** * Part of the {@link Provable} interface. * @@ -470,4 +350,36 @@ class Group { }`; } } + + static toInput(x: Group) { + return { + fields: [x.x, x.y], + }; + } +} + +// internal helpers + +function isConstant(g: Group) { + return g.x.isConstant() && g.y.isConstant(); +} + +function toTuple(g: Group): [0, FieldVar, FieldVar] { + return [0, g.x.value, g.y.value]; +} + +function toProjective(g: Group) { + return Pallas.fromAffine({ + x: g.x.toBigInt(), + y: g.y.toBigInt(), + infinity: false, + }); +} + +function fromProjective({ x, y, z }: { x: bigint; y: bigint; z: bigint }) { + return fromAffine(Pallas.toAffine({ x, y, z })); +} + +function fromAffine({ x, y, infinity }: GroupAffine) { + return infinity ? Group.zero : new Group({ x, y }); } diff --git a/src/lib/provable/int.ts b/src/lib/provable/int.ts new file mode 100644 index 0000000000..2b2fa32110 --- /dev/null +++ b/src/lib/provable/int.ts @@ -0,0 +1,1587 @@ +import { Field, Bool } from './wrapped.js'; +import { AnyConstructor, Struct } from './types/struct.js'; +import { Types } from '../../bindings/mina-transaction/types.js'; +import { HashInput } from './crypto/poseidon.js'; +import { Provable } from './provable.js'; +import * as RangeCheck from './gadgets/range-check.js'; +import * as Bitwise from './gadgets/bitwise.js'; +import { addMod32 } from './gadgets/arithmetic.js'; +import type { Gadgets } from './gadgets/gadgets.js'; +import { withMessage } from './field.js'; +import { FieldVar } from './core/fieldvar.js'; +import { CircuitValue, prop } from './types/circuit-value.js'; +import { + assertLessThanGeneric, + assertLessThanOrEqualGeneric, + lessThanGeneric, + lessThanOrEqualGeneric, +} from './gadgets/comparison.js'; +import { assert } from '../util/assert.js'; + +// external API +export { UInt8, UInt32, UInt64, Int64, Sign }; + +/** + * A 64 bit unsigned integer with values ranging from 0 to 18,446,744,073,709,551,615. + */ +class UInt64 extends CircuitValue { + @prop value: Field; + static NUM_BITS = 64; + + /** + * Create a {@link UInt64}. + * The max value of a {@link UInt64} is `2^64 - 1 = UInt64.MAXINT()`. + * + * **Warning**: Cannot overflow, an error is thrown if the result is greater than UInt64.MAXINT() + */ + constructor(x: UInt64 | UInt32 | FieldVar | number | string | bigint) { + if (x instanceof UInt64 || x instanceof UInt32) x = x.value.value; + let value = Field(x); + super(value); + // check the range if the argument is a constant + UInt64.checkConstant(value); + } + + static Unsafe = { + /** + * Create a {@link UInt64} from a {@link Field} without constraining its range. + * + * **Warning**: This is unsafe, because it does not prove that the input {@link Field} actually fits in 64 bits.\ + * Only use this if you know what you are doing, otherwise use the safe {@link UInt64.from}. + */ + fromField(x: Field) { + return new UInt64(x.value); + }, + }; + + /** + * Static method to create a {@link UInt64} with value `0`. + */ + static get zero() { + return new UInt64(0); + } + /** + * Static method to create a {@link UInt64} with value `1`. + */ + static get one() { + return new UInt64(1); + } + /** + * Turns the {@link UInt64} into a string. + * @returns + */ + toString() { + return this.value.toString(); + } + /** + * Turns the {@link UInt64} into a {@link BigInt}. + * @returns + */ + toBigInt() { + return this.value.toBigInt(); + } + + /** + * Turns the {@link UInt64} into a {@link UInt32}, asserting that it fits in 32 bits. + */ + toUInt32() { + let uint32 = new UInt32(this.value.value); + UInt32.check(uint32); + return uint32; + } + + /** + * Turns the {@link UInt64} into a {@link UInt32}, clamping to the 32 bits range if it's too large. + * ```ts + * UInt64.from(4294967296).toUInt32Clamped().toString(); // "4294967295" + * ``` + */ + toUInt32Clamped() { + let max = (1n << 32n) - 1n; + let field = Provable.if( + this.greaterThan(UInt64.from(max)), + Field.from(max), + this.value + ); + return UInt32.Unsafe.fromField(field); + } + + static check(x: UInt64) { + RangeCheck.rangeCheckN(UInt64.NUM_BITS, x.value); + } + + static toInput(x: UInt64): HashInput { + return { packed: [[x.value, 64]] }; + } + + /** + * Encodes this structure into a JSON-like object. + */ + static toJSON(x: UInt64) { + return x.value.toString(); + } + + /** + * Decodes a JSON-like object into this structure. + */ + static fromJSON(x: string): InstanceType { + return this.from(x) as any; + } + + private static checkConstant(x: Field) { + if (!x.isConstant()) return x; + let xBig = x.toBigInt(); + if (xBig < 0n || xBig >= 1n << BigInt(this.NUM_BITS)) { + throw Error( + `UInt64: Expected number between 0 and 2^64 - 1, got ${xBig}` + ); + } + return x; + } + + /** + * Creates a new {@link UInt64}. + */ + static from(x: UInt64 | UInt32 | number | string | bigint) { + if (x instanceof UInt64) return x; + return new this(x); + } + + /** + * Creates a {@link UInt64} with a value of 18,446,744,073,709,551,615. + */ + static MAXINT() { + return new UInt64((1n << 64n) - 1n); + } + + /** + * Integer division with remainder. + * + * `x.divMod(y)` returns the quotient and the remainder. + */ + divMod(y: UInt64 | number | string) { + let x = this.value; + let y_ = UInt64.from(y).value; + + if (this.value.isConstant() && y_.isConstant()) { + let xn = x.toBigInt(); + let yn = y_.toBigInt(); + let q = xn / yn; + let r = xn - q * yn; + return { + quotient: new UInt64(q), + rest: new UInt64(r), + }; + } + + y_ = y_.seal(); + + let q = Provable.witness( + Field, + () => new Field(x.toBigInt() / y_.toBigInt()) + ); + + RangeCheck.rangeCheckN(UInt64.NUM_BITS, q); + + // TODO: Could be a bit more efficient + let r = x.sub(q.mul(y_)).seal(); + RangeCheck.rangeCheckN(UInt64.NUM_BITS, r); + + let r_ = new UInt64(r.value); + let q_ = new UInt64(q.value); + + r_.assertLessThan(new UInt64(y_.value)); + + return { quotient: q_, rest: r_ }; + } + + /** + * Integer division. + * + * `x.div(y)` returns the floor of `x / y`, that is, the greatest + * `z` such that `z * y <= x`. + * + */ + div(y: UInt64 | number) { + return this.divMod(y).quotient; + } + + /** + * Integer remainder. + * + * `x.mod(y)` returns the value `z` such that `0 <= z < y` and + * `x - z` is divisible by `y`. + */ + mod(y: UInt64 | number) { + return this.divMod(y).rest; + } + + /** + * Multiplication with overflow checking. + */ + mul(y: UInt64 | number) { + let z = this.value.mul(UInt64.from(y).value); + RangeCheck.rangeCheckN(UInt64.NUM_BITS, z); + return new UInt64(z.value); + } + + /** + * Addition with overflow checking. + */ + add(y: UInt64 | number) { + let z = this.value.add(UInt64.from(y).value); + RangeCheck.rangeCheckN(UInt64.NUM_BITS, z); + return new UInt64(z.value); + } + + /** + * Subtraction with underflow checking. + */ + sub(y: UInt64 | number) { + let z = this.value.sub(UInt64.from(y).value); + RangeCheck.rangeCheckN(UInt64.NUM_BITS, z); + return new UInt64(z.value); + } + + /** + * Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). + * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. + * + * This gadget builds a chain of XOR gates recursively. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#xor-1) + * + * @param x {@link UInt64} element to XOR. + * + * @example + * ```ts + * let a = UInt64.from(0b0101); + * let b = UInt64.from(0b0011); + * + * let c = a.xor(b); + * c.assertEquals(0b0110); + * ``` + */ + xor(x: UInt64) { + return new UInt64(Bitwise.xor(this.value, x.value, UInt64.NUM_BITS).value); + } + + /** + * Bitwise NOT gate on {@link Field} elements. Similar to the [bitwise + * NOT `~` operator in JavaScript](https://developer.mozilla.org/en-US/docs/ + * Web/JavaScript/Reference/Operators/Bitwise_NOT). + * + * **Note:** The NOT gate operates over 64 bit for UInt64 types. + * + * A NOT gate works by returning `1` in each bit position if the + * corresponding bit of the operand is `0`, and returning `0` if the + * corresponding bit of the operand is `1`. + * + * NOT is implemented as a subtraction of the input from the all one bitmask + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#not) + * + * @example + * ```ts + * // NOTing 4 bits with the unchecked version + * let a = UInt64.from(0b0101); + * let b = a.not(false); + * + * console.log(b.toBigInt().toString(2)); + * // 1111111111111111111111111111111111111111111111111111111111111010 + * + * ``` + * + * @param a - The value to apply NOT to. + * + */ + not() { + return new UInt64(Bitwise.not(this.value, UInt64.NUM_BITS, false).value); + } + + /** + * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, + * with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded. + * For a left rotation, this means that bits shifted off the left end reappear at the right end. + * Conversely, for a right rotation, bits shifted off the right end reappear at the left end. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * The `direction` parameter is a string that accepts either `'left'` or `'right'`, determining the direction of the rotation. + * + * To safely use `rotate()`, you need to make sure that the value passed in is range-checked to 64 bits; + * for example, using {@link Gadgets.rangeCheck64}. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#rotation) + * + * @param bits amount of bits to rotate this {@link UInt64} element with. + * @param direction left or right rotation direction. + * + * + * @example + * ```ts + * const x = UInt64.from(0b001100); + * const y = x.rotate(2, 'left'); + * const z = x.rotate(2, 'right'); // right rotation by 2 bits + * y.assertEquals(0b110000); + * z.assertEquals(0b000011); + * ``` + */ + rotate(bits: number, direction: 'left' | 'right' = 'left') { + return new UInt64(Bitwise.rotate64(this.value, bits, direction).value); + } + + /** + * Performs a left shift operation on the provided {@link UInt64} element. + * This operation is similar to the `<<` shift operation in JavaScript, + * where bits are shifted to the left, and the overflowing bits are discarded. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * + * @param bits Amount of bits to shift the {@link UInt64} element to the left. The amount should be between 0 and 64 (or else the shift will fail). + * + * @example + * ```ts + * const x = UInt64.from(0b001100); // 12 in binary + * const y = x.leftShift(2); // left shift by 2 bits + * y.assertEquals(0b110000); // 48 in binary + * ``` + */ + leftShift(bits: number) { + return new UInt64(Bitwise.leftShift64(this.value, bits).value); + } + + /** + * Performs a left right operation on the provided {@link UInt64} element. + * This operation is similar to the `>>` shift operation in JavaScript, + * where bits are shifted to the right, and the overflowing bits are discarded. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * + * @param bits Amount of bits to shift the {@link UInt64} element to the right. The amount should be between 0 and 64 (or else the shift will fail). + * + * @example + * ```ts + * const x = UInt64.from(0b001100); // 12 in binary + * const y = x.rightShift(2); // left shift by 2 bits + * y.assertEquals(0b000011); // 48 in binary + * ``` + */ + rightShift(bits: number) { + return new UInt64(Bitwise.leftShift64(this.value, bits).value); + } + + /** + * Bitwise AND gadget on {@link UInt64} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND). + * The AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise. + * + * It can be checked by a double generic gate that verifies the following relationship between the values below. + * + * The generic gate verifies:\ + * `a + b = sum` and the conjunction equation `2 * and = sum - xor`\ + * Where:\ + * `a + b = sum`\ + * `a ^ b = xor`\ + * `a & b = and` + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) + * + * + * @example + * ```typescript + * let a = UInt64.from(3); // ... 000011 + * let b = UInt64.from(5); // ... 000101 + * + * let c = a.and(b); // ... 000001 + * c.assertEquals(1); + * ``` + */ + and(x: UInt64) { + return new UInt64(Bitwise.and(this.value, x.value, UInt64.NUM_BITS).value); + } + + /** + * Checks if a {@link UInt64} is less than or equal to another one. + */ + lessThanOrEqual(y: UInt64) { + if (this.value.isConstant() && y.value.isConstant()) { + return Bool(this.value.toBigInt() <= y.value.toBigInt()); + } + return lessThanOrEqualGeneric(this.value, y.value, 1n << 64n, (v) => + RangeCheck.rangeCheckN(UInt64.NUM_BITS, v) + ); + } + + /** + * Asserts that a {@link UInt64} is less than or equal to another one. + */ + assertLessThanOrEqual(y: UInt64, message?: string) { + if (this.value.isConstant() && y.value.isConstant()) { + let [x0, y0] = [this.value.toBigInt(), y.value.toBigInt()]; + return assert( + x0 <= y0, + message ?? `UInt64.assertLessThanOrEqual: expected ${x0} <= ${y0}` + ); + } + assertLessThanOrEqualGeneric(this.value, y.value, (v) => + RangeCheck.rangeCheckN(UInt64.NUM_BITS, v, message) + ); + } + + /** + * + * Checks if a {@link UInt64} is less than another one. + */ + lessThan(y: UInt64) { + if (this.value.isConstant() && y.value.isConstant()) { + return Bool(this.value.toBigInt() < y.value.toBigInt()); + } + return lessThanGeneric(this.value, y.value, 1n << 64n, (v) => + RangeCheck.rangeCheckN(UInt64.NUM_BITS, v) + ); + } + + /** + * Asserts that a {@link UInt64} is less than another one. + */ + assertLessThan(y: UInt64, message?: string) { + if (this.value.isConstant() && y.value.isConstant()) { + let [x0, y0] = [this.value.toBigInt(), y.value.toBigInt()]; + return assert( + x0 < y0, + message ?? `UInt64.assertLessThan: expected ${x0} < ${y0}` + ); + } + assertLessThanGeneric(this.value, y.value, (v) => + RangeCheck.rangeCheckN(UInt64.NUM_BITS, v, message) + ); + } + + /** + * Checks if a {@link UInt64} is greater than another one. + */ + greaterThan(y: UInt64) { + return y.lessThan(this); + } + + /** + * Asserts that a {@link UInt64} is greater than another one. + */ + assertGreaterThan(y: UInt64, message?: string) { + y.assertLessThan(this, message); + } + + /** + * Checks if a {@link UInt64} is greater than or equal to another one. + */ + greaterThanOrEqual(y: UInt64) { + return y.lessThanOrEqual(this); + } + + /** + * Asserts that a {@link UInt64} is greater than or equal to another one. + */ + assertGreaterThanOrEqual(y: UInt64, message?: string) { + y.assertLessThanOrEqual(this, message); + } +} +/** + * A 32 bit unsigned integer with values ranging from 0 to 4,294,967,295. + */ +class UInt32 extends CircuitValue { + @prop value: Field; + static NUM_BITS = 32; + + /** + * Create a {@link UInt32}. + * The max value of a {@link UInt32} is `2^32 - 1 = UInt32.MAXINT()`. + * + * **Warning**: Cannot overflow, an error is thrown if the result is greater than UInt32.MAXINT() + */ + constructor(x: UInt32 | FieldVar | number | string | bigint) { + if (x instanceof UInt32) x = x.value.value; + let value = Field(x); + super(value); + // check the range if the argument is a constant + UInt32.checkConstant(value); + } + + static Unsafe = { + /** + * Create a {@link UInt32} from a {@link Field} without constraining its range. + * + * **Warning**: This is unsafe, because it does not prove that the input {@link Field} actually fits in 32 bits.\ + * Only use this if you know what you are doing, otherwise use the safe {@link UInt32.from}. + */ + fromField(x: Field) { + return new UInt32(x.value); + }, + }; + + /** + * Static method to create a {@link UInt32} with value `0`. + */ + static get zero(): UInt32 { + return new UInt32(0); + } + + /** + * Static method to create a {@link UInt32} with value `0`. + */ + static get one(): UInt32 { + return new UInt32(1); + } + /** + * Turns the {@link UInt32} into a string. + */ + toString(): string { + return this.value.toString(); + } + /** + * Turns the {@link UInt32} into a {@link BigInt}. + */ + toBigint() { + return this.value.toBigInt(); + } + /** + * Turns the {@link UInt32} into a {@link UInt64}. + */ + toUInt64(): UInt64 { + // this is safe, because the UInt32 range is included in the UInt64 range + return new UInt64(this.value.value); + } + + static check(x: UInt32) { + RangeCheck.rangeCheck32(x.value); + } + static toInput(x: UInt32): HashInput { + return { packed: [[x.value, 32]] }; + } + /** + * Encodes this structure into a JSON-like object. + */ + static toJSON(x: UInt32) { + return x.value.toString(); + } + + /** + * Decodes a JSON-like object into this structure. + */ + static fromJSON(x: string): InstanceType { + return this.from(x) as any; + } + + private static checkConstant(x: Field) { + if (!x.isConstant()) return x; + let xBig = x.toBigInt(); + if (xBig < 0n || xBig >= 1n << BigInt(this.NUM_BITS)) { + throw Error( + `UInt32: Expected number between 0 and 2^32 - 1, got ${xBig}` + ); + } + return x; + } + + // this checks the range if the argument is a constant + /** + * Creates a new {@link UInt32}. + */ + static from(x: UInt32 | number | string | bigint) { + if (x instanceof UInt32) return x; + return new this(x); + } + + /** + * Creates a {@link UInt32} with a value of 4,294,967,295. + */ + static MAXINT() { + return new UInt32((1n << 32n) - 1n); + } + + /** + * Addition modulo 2^32. Check {@link Gadgets.addMod32} for a detailed description. + */ + addMod32(y: UInt32) { + return new UInt32(addMod32(this.value, y.value).value); + } + + /** + * Integer division with remainder. + * + * `x.divMod(y)` returns the quotient and the remainder. + */ + divMod(y: UInt32 | number | string) { + let x = this.value; + let y_ = UInt32.from(y).value; + + if (x.isConstant() && y_.isConstant()) { + let xn = x.toBigInt(); + let yn = y_.toBigInt(); + let q = xn / yn; + let r = xn - q * yn; + return { + quotient: new UInt32(new Field(q.toString()).value), + rest: new UInt32(new Field(r.toString()).value), + }; + } + + y_ = y_.seal(); + + let q = Provable.witness( + Field, + () => new Field(x.toBigInt() / y_.toBigInt()) + ); + + RangeCheck.rangeCheck32(q); + + // TODO: Could be a bit more efficient + let r = x.sub(q.mul(y_)).seal(); + RangeCheck.rangeCheck32(r); + + let r_ = new UInt32(r.value); + let q_ = new UInt32(q.value); + + r_.assertLessThan(new UInt32(y_.value)); + + return { quotient: q_, rest: r_ }; + } + /** + * Integer division. + * + * `x.div(y)` returns the floor of `x / y`, that is, the greatest + * `z` such that `x * y <= x`. + * + */ + div(y: UInt32 | number) { + return this.divMod(y).quotient; + } + /** + * Integer remainder. + * + * `x.mod(y)` returns the value `z` such that `0 <= z < y` and + * `x - z` is divisible by `y`. + */ + mod(y: UInt32 | number) { + return this.divMod(y).rest; + } + /** + * Multiplication with overflow checking. + */ + mul(y: UInt32 | number) { + let z = this.value.mul(UInt32.from(y).value); + RangeCheck.rangeCheck32(z); + return new UInt32(z.value); + } + /** + * Addition with overflow checking. + */ + add(y: UInt32 | number) { + let z = this.value.add(UInt32.from(y).value); + RangeCheck.rangeCheck32(z); + return new UInt32(z.value); + } + /** + * Subtraction with underflow checking. + */ + sub(y: UInt32 | number) { + let z = this.value.sub(UInt32.from(y).value); + RangeCheck.rangeCheck32(z); + return new UInt32(z.value); + } + + /** + * Bitwise XOR gadget on {@link UInt32} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). + * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. + * + * This gadget builds a chain of XOR gates recursively. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#xor-1) + * + * @param x {@link UInt32} element to compare. + * + * @example + * ```ts + * let a = UInt32.from(0b0101); + * let b = UInt32.from(0b0011); + * + * let c = a.xor(b); + * c.assertEquals(0b0110); + * ``` + */ + xor(x: UInt32) { + return new UInt32(Bitwise.xor(this.value, x.value, UInt32.NUM_BITS).value); + } + + /** + * Bitwise NOT gate on {@link UInt32} elements. Similar to the [bitwise + * NOT `~` operator in JavaScript](https://developer.mozilla.org/en-US/docs/ + * Web/JavaScript/Reference/Operators/Bitwise_NOT). + * + * **Note:** The NOT gate operates over 32 bit for UInt32 types. + * + * A NOT gate works by returning `1` in each bit position if the + * corresponding bit of the operand is `0`, and returning `0` if the + * corresponding bit of the operand is `1`. + * + * NOT is implemented as a subtraction of the input from the all one bitmask. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#not) + * + * @example + * ```ts + * // NOTing 4 bits with the unchecked version + * let a = UInt32.from(0b0101); + * let b = a.not(); + * + * console.log(b.toBigInt().toString(2)); + * // 11111111111111111111111111111010 + * ``` + * + * @param a - The value to apply NOT to. + */ + not() { + return new UInt32(Bitwise.not(this.value, UInt32.NUM_BITS, false).value); + } + + /** + * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, + * with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded. + * For a left rotation, this means that bits shifted off the left end reappear at the right end. + * Conversely, for a right rotation, bits shifted off the right end reappear at the left end. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * The `direction` parameter is a string that accepts either `'left'` or `'right'`, determining the direction of the rotation. + * + * To safely use `rotate()`, you need to make sure that the value passed in is range-checked to 64 bits; + * for example, using {@link Gadgets.rangeCheck64}. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#rotation) + * + * @param bits amount of bits to rotate this {@link UInt32} element with. + * @param direction left or right rotation direction. + * + * + * @example + * ```ts + * const x = UInt32.from(0b001100); + * const y = x.rotate(2, 'left'); + * const z = x.rotate(2, 'right'); // right rotation by 2 bits + * y.assertEquals(0b110000); + * z.assertEquals(0b000011); + * ``` + */ + rotate(bits: number, direction: 'left' | 'right' = 'left') { + return new UInt32(Bitwise.rotate32(this.value, bits, direction).value); + } + + /** + * Performs a left shift operation on the provided {@link UInt32} element. + * This operation is similar to the `<<` shift operation in JavaScript, + * where bits are shifted to the left, and the overflowing bits are discarded. + * + * It’s important to note that these operations are performed considering the big-endian 32-bit representation of the number, + * where the most significant (32th) bit is on the left end and the least significant bit is on the right end. + * + * The operation expects the input to be range checked to 32 bit. + * + * @param bits Amount of bits to shift the {@link UInt32} element to the left. The amount should be between 0 and 32 (or else the shift will fail). + * + * @example + * ```ts + * const x = UInt32.from(0b001100); // 12 in binary + * const y = x.leftShift(2); // left shift by 2 bits + * y.assertEquals(0b110000); // 48 in binary + * ``` + */ + leftShift(bits: number) { + return new UInt32(Bitwise.leftShift32(this.value, bits).value); + } + + /** + * Performs a left right operation on the provided {@link UInt32} element. + * This operation is similar to the `>>` shift operation in JavaScript, + * where bits are shifted to the right, and the overflowing bits are discarded. + * + * It’s important to note that these operations are performed considering the big-endian 32-bit representation of the number, + * where the most significant (32th) bit is on the left end and the least significant bit is on the right end. + * + * @param bits Amount of bits to shift the {@link UInt32} element to the right. The amount should be between 0 and 32 (or else the shift will fail). + * + * The operation expects the input to be range checked to 32 bit. + * + * @example + * ```ts + * const x = UInt32.from(0b001100); // 12 in binary + * const y = x.rightShift(2); // left shift by 2 bits + * y.assertEquals(0b000011); // 48 in binary + * ``` + */ + rightShift(bits: number) { + return new UInt32(Bitwise.rightShift64(this.value, bits).value); + } + + /** + * Bitwise AND gadget on {@link UInt32} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND). + * The AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise. + * + * It can be checked by a double generic gate that verifies the following relationship between the values below. + * + * The generic gate verifies:\ + * `a + b = sum` and the conjunction equation `2 * and = sum - xor`\ + * Where:\ + * `a + b = sum`\ + * `a ^ b = xor`\ + * `a & b = and` + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) + * + * + * @example + * ```typescript + * let a = UInt32.from(3); // ... 000011 + * let b = UInt32.from(5); // ... 000101 + * + * let c = a.and(b, 2); // ... 000001 + * c.assertEquals(1); + * ``` + */ + and(x: UInt32) { + return new UInt32(Bitwise.and(this.value, x.value, UInt32.NUM_BITS).value); + } + + /** + * Checks if a {@link UInt32} is less than or equal to another one. + */ + lessThanOrEqual(y: UInt32) { + if (this.value.isConstant() && y.value.isConstant()) { + return Bool(this.value.toBigInt() <= y.value.toBigInt()); + } + return lessThanOrEqualGeneric(this.value, y.value, 1n << 32n, (v) => + RangeCheck.rangeCheckN(UInt32.NUM_BITS, v) + ); + } + + /** + * Asserts that a {@link UInt32} is less than or equal to another one. + */ + assertLessThanOrEqual(y: UInt32, message?: string) { + if (this.value.isConstant() && y.value.isConstant()) { + let [x0, y0] = [this.value.toBigInt(), y.value.toBigInt()]; + return assert( + x0 <= y0, + message ?? `UInt32.assertLessThanOrEqual: expected ${x0} <= ${y0}` + ); + } + assertLessThanOrEqualGeneric(this.value, y.value, (v) => + RangeCheck.rangeCheckN(UInt32.NUM_BITS, v, message) + ); + } + + /** + * Checks if a {@link UInt32} is less than another one. + */ + lessThan(y: UInt32) { + if (this.value.isConstant() && y.value.isConstant()) { + return Bool(this.value.toBigInt() < y.value.toBigInt()); + } + return lessThanGeneric(this.value, y.value, 1n << 64n, (v) => + RangeCheck.rangeCheckN(UInt64.NUM_BITS, v) + ); + } + + /** + * Asserts that a {@link UInt32} is less than another one. + */ + assertLessThan(y: UInt32, message?: string) { + if (this.value.isConstant() && y.value.isConstant()) { + let [x0, y0] = [this.value.toBigInt(), y.value.toBigInt()]; + return assert( + x0 < y0, + message ?? `UInt32.assertLessThan: expected ${x0} < ${y0}` + ); + } + assertLessThanGeneric(this.value, y.value, (v) => + RangeCheck.rangeCheckN(UInt32.NUM_BITS, v, message) + ); + } + + /** + * Checks if a {@link UInt32} is greater than another one. + */ + greaterThan(y: UInt32) { + return y.lessThan(this); + } + + /** + * Asserts that a {@link UInt32} is greater than another one. + */ + assertGreaterThan(y: UInt32, message?: string) { + y.assertLessThan(this, message); + } + + /** + * Checks if a {@link UInt32} is greater than or equal to another one. + */ + greaterThanOrEqual(y: UInt32) { + return y.lessThanOrEqual(this); + } + + /** + * Asserts that a {@link UInt32} is greater than or equal to another one. + */ + assertGreaterThanOrEqual(y: UInt32, message?: string) { + y.assertLessThanOrEqual(this, message); + } +} + +class Sign extends CircuitValue { + @prop value: Field; // +/- 1 + + static get one() { + return new Sign(Field(1)); + } + static get minusOne() { + return new Sign(Field(-1)); + } + static check(x: Sign) { + // x^2 === 1 <=> x === 1 or x === -1 + x.value.square().assertEquals(Field(1)); + } + static empty(): InstanceType { + return Sign.one as any; + } + static toInput(x: Sign): HashInput { + return { packed: [[x.isPositive().toField(), 1]] }; + } + static toJSON(x: Sign) { + if (x.toString() === '1') return 'Positive'; + if (x.neg().toString() === '1') return 'Negative'; + throw Error(`Invalid Sign: ${x}`); + } + static fromJSON( + x: 'Positive' | 'Negative' + ): InstanceType { + return (x === 'Positive' ? new Sign(Field(1)) : new Sign(Field(-1))) as any; + } + neg() { + return new Sign(this.value.neg()); + } + mul(y: Sign) { + return new Sign(this.value.mul(y.value)); + } + isPositive() { + return this.value.equals(Field(1)); + } + toString() { + return this.value.toString(); + } +} + +type BalanceChange = Types.AccountUpdate['body']['balanceChange']; + +/** + * A 64 bit signed integer with values ranging from -18,446,744,073,709,551,615 to 18,446,744,073,709,551,615. + */ +class Int64 extends CircuitValue implements BalanceChange { + // * in the range [-2^64+1, 2^64-1], unlike a normal int64 + // * under- and overflowing is disallowed, similar to UInt64, unlike a normal int64+ + + @prop magnitude: UInt64; // absolute value + @prop sgn: Sign; // +/- 1 + + // Some thoughts regarding the representation as field elements: + // toFields returns the in-circuit representation, so the main objective is to minimize the number of constraints + // that result from this representation. Therefore, I think the only candidate for an efficient 1-field representation + // is the one where the Int64 is the field: toFields = Int64 => [Int64.magnitude.mul(Int64.sign)]. Anything else involving + // bit packing would just lead to very inefficient circuit operations. + // + // So, is magnitude * sign ("1-field") a more efficient representation than (magnitude, sign) ("2-field")? + // Several common operations like add, mul, etc, operate on 1-field so in 2-field they result in one additional multiplication + // constraint per operand. However, the check operation (constraining to 64 bits + a sign) which is called at the introduction + // of every witness, and also at the end of add, mul, etc, operates on 2-field. So here, the 1-field representation needs + // to add an additional magnitude * sign = Int64 multiplication constraint, which will typically cancel out most of the gains + // achieved by 1-field elsewhere. + // There are some notable operations for which 2-field is definitely better: + // + // * div and mod (which do integer division with rounding on the magnitude) + // * converting the Int64 to a Currency.Amount.Signed (for the zkapp balance), which has the exact same (magnitude, sign) representation we use here. + // + // The second point is one of the main things an Int64 is used for, and was the original motivation to use 2 fields. + // Overall, I think the existing implementation is the optimal one. + + constructor(magnitude: UInt64, sgn = Sign.one) { + super(magnitude, sgn); + } + + /** + * Creates a new {@link Int64} from a {@link Field}. + * + * Does check if the {@link Field} is within range. + */ + private static fromFieldUnchecked(x: Field) { + let TWO64 = 1n << 64n; + let xBigInt = x.toBigInt(); + let isValidPositive = xBigInt < TWO64; // covers {0,...,2^64 - 1} + let isValidNegative = Field.ORDER - xBigInt < TWO64; // {-2^64 + 1,...,-1} + if (!isValidPositive && !isValidNegative) + throw Error(`Int64: Expected a value between (-2^64, 2^64), got ${x}`); + let magnitude = Field(isValidPositive ? x.toString() : x.neg().toString()); + let sign = isValidPositive ? Sign.one : Sign.minusOne; + return new Int64(new UInt64(magnitude.value), sign); + } + + // this doesn't check ranges because we assume they're already checked on UInts + /** + * Creates a new {@link Int64} from a {@link Field}. + * + * **Does not** check if the {@link Field} is within range. + */ + static fromUnsigned(x: UInt64 | UInt32) { + return new Int64(x instanceof UInt32 ? x.toUInt64() : x); + } + + // this checks the range if the argument is a constant + /** + * Creates a new {@link Int64}. + * + * Check the range if the argument is a constant. + */ + static from(x: Int64 | UInt32 | UInt64 | Field | number | string | bigint) { + if (x instanceof Int64) return x; + if (x instanceof UInt64 || x instanceof UInt32) { + return Int64.fromUnsigned(x); + } + return Int64.fromFieldUnchecked(Field(x)); + } + /** + * Turns the {@link Int64} into a string. + */ + toString() { + let abs = this.magnitude.toString(); + let sgn = this.isPositive().toBoolean() || abs === '0' ? '' : '-'; + return sgn + abs; + } + isConstant() { + return this.magnitude.value.isConstant() && this.sgn.isConstant(); + } + + // --- circuit-compatible operations below --- + // the assumption here is that all Int64 values that appear in a circuit are already checked as valid + // this is because Provable.witness calls .check, which calls .check on each prop, i.e. UInt64 and Sign + // so we only have to do additional checks if an operation on valid inputs can have an invalid outcome (example: overflow) + /** + * Static method to create a {@link Int64} with value `0`. + */ + static get zero() { + return new Int64(UInt64.zero); + } + /** + * Static method to create a {@link Int64} with value `1`. + */ + static get one() { + return new Int64(UInt64.one); + } + /** + * Static method to create a {@link Int64} with value `-1`. + */ + static get minusOne() { + return new Int64(UInt64.one).neg(); + } + + /** + * Returns the {@link Field} value. + */ + toField() { + return this.magnitude.value.mul(this.sgn.value); + } + /** + * Static method to create a {@link Int64} from a {@link Field}. + */ + static fromField(x: Field): Int64 { + // constant case - just return unchecked value + if (x.isConstant()) return Int64.fromFieldUnchecked(x); + // variable case - create a new checked witness and prove consistency with original field + let xInt = Provable.witness(Int64, () => Int64.fromFieldUnchecked(x)); + xInt.toField().assertEquals(x); // sign(x) * |x| === x + return xInt; + } + + /** + * Negates the value. + * + * `Int64.from(5).neg()` will turn into `Int64.from(-5)` + */ + neg() { + // doesn't need further check if `this` is valid + return new Int64(this.magnitude, this.sgn.neg()); + } + /** + * Addition with overflow checking. + */ + add(y: Int64 | number | string | bigint | UInt64 | UInt32) { + let y_ = Int64.from(y); + return Int64.fromField(this.toField().add(y_.toField())); + } + /** + * Subtraction with underflow checking. + */ + sub(y: Int64 | number | string | bigint | UInt64 | UInt32) { + let y_ = Int64.from(y); + return Int64.fromField(this.toField().sub(y_.toField())); + } + /** + * Multiplication with overflow checking. + */ + mul(y: Int64 | number | string | bigint | UInt64 | UInt32) { + let y_ = Int64.from(y); + return Int64.fromField(this.toField().mul(y_.toField())); + } + /** + * Integer division. + * + * `x.div(y)` returns the floor of `x / y`, that is, the greatest + * `z` such that `z * y <= x`. + * + */ + div(y: Int64 | number | string | bigint | UInt64 | UInt32) { + let y_ = Int64.from(y); + let { quotient } = this.magnitude.divMod(y_.magnitude); + let sign = this.sgn.mul(y_.sgn); + return new Int64(quotient, sign); + } + /** + * Integer remainder. + * + * `x.mod(y)` returns the value `z` such that `0 <= z < y` and + * `x - z` is divisible by `y`. + */ + mod(y: UInt64 | number | string | bigint | UInt32) { + let y_ = UInt64.from(y); + let rest = this.magnitude.divMod(y_).rest.value; + rest = Provable.if(this.isPositive(), rest, y_.value.sub(rest)); + return new Int64(new UInt64(rest.value)); + } + + /** + * Checks if two values are equal. + */ + equals(y: Int64 | number | string | bigint | UInt64 | UInt32) { + let y_ = Int64.from(y); + return this.toField().equals(y_.toField()); + } + /** + * Asserts that two values are equal. + */ + assertEquals( + y: Int64 | number | string | bigint | UInt64 | UInt32, + message?: string + ) { + let y_ = Int64.from(y); + this.toField().assertEquals(y_.toField(), message); + } + /** + * Checks if the value is positive. + */ + isPositive() { + return this.sgn.isPositive(); + } +} + +/** + * A 8 bit unsigned integer with values ranging from 0 to 255. + */ +class UInt8 extends Struct({ + value: Field, +}) { + static NUM_BITS = 8; + + /** + * Create a {@link UInt8} from a bigint or number. + * The max value of a {@link UInt8} is `2^8 - 1 = 255`. + * + * **Warning**: Cannot overflow past 255, an error is thrown if the result is greater than 255. + */ + constructor(x: number | bigint | FieldVar | UInt8) { + if (x instanceof UInt8) x = x.value.value; + super({ value: Field(x) }); + UInt8.checkConstant(this.value); + } + + static Unsafe = { + /** + * Create a {@link UInt8} from a {@link Field} without constraining its range. + * + * **Warning**: This is unsafe, because it does not prove that the input {@link Field} actually fits in 8 bits.\ + * Only use this if you know what you are doing, otherwise use the safe {@link UInt8.from}. + */ + fromField(x: Field) { + return new UInt8(x.value); + }, + }; + + /** + * Add a {@link UInt8} to another {@link UInt8} without allowing overflow. + * + * @example + * ```ts + * const x = UInt8.from(3); + * const sum = x.add(5); + * sum.assertEquals(8); + * ``` + * + * @throws if the result is greater than 255. + */ + add(y: UInt8 | bigint | number) { + let z = this.value.add(UInt8.from(y).value); + RangeCheck.rangeCheck8(z); + return UInt8.Unsafe.fromField(z); + } + + /** + * Subtract a {@link UInt8} from another {@link UInt8} without allowing underflow. + * + * @example + * ```ts + * const x = UInt8.from(8); + * const difference = x.sub(5); + * difference.assertEquals(3); + * ``` + * + * @throws if the result is less than 0. + */ + sub(y: UInt8 | bigint | number) { + let z = this.value.sub(UInt8.from(y).value); + RangeCheck.rangeCheck8(z); + return UInt8.Unsafe.fromField(z); + } + + /** + * Multiply a {@link UInt8} by another {@link UInt8} without allowing overflow. + * + * @example + * ```ts + * const x = UInt8.from(3); + * const product = x.mul(5); + * product.assertEquals(15); + * ``` + * + * @throws if the result is greater than 255. + */ + mul(y: UInt8 | bigint | number) { + let z = this.value.mul(UInt8.from(y).value); + RangeCheck.rangeCheck8(z); + return UInt8.Unsafe.fromField(z); + } + + /** + * Divide a {@link UInt8} by another {@link UInt8}. + * This is integer division that rounds down. + * + * @example + * ```ts + * const x = UInt8.from(7); + * const quotient = x.div(2); + * quotient.assertEquals(3); + * ``` + */ + div(y: UInt8 | bigint | number) { + return this.divMod(y).quotient; + } + + /** + * Get the remainder a {@link UInt8} of division of another {@link UInt8}. + * + * @example + * ```ts + * const x = UInt8.from(50); + * const mod = x.mod(30); + * mod.assertEquals(20); + * ``` + */ + mod(y: UInt8 | bigint | number) { + return this.divMod(y).remainder; + } + + /** + * Get the quotient and remainder of a {@link UInt8} divided by another {@link UInt8}: + * + * `x == y * q + r`, where `0 <= r < y`. + * + * @param y - a {@link UInt8} to get the quotient and remainder of another {@link UInt8}. + * + * @return The quotient `q` and remainder `r`. + */ + divMod(y: UInt8 | bigint | number) { + let x = this.value; + let y_ = UInt8.from(y).value.seal(); + + if (this.value.isConstant() && y_.isConstant()) { + let xn = x.toBigInt(); + let yn = y_.toBigInt(); + let q = xn / yn; + let r = xn - q * yn; + return { quotient: UInt8.from(q), remainder: UInt8.from(r) }; + } + + // prove that x === q * y + r, where 0 <= r < y + let q = Provable.witness(Field, () => Field(x.toBigInt() / y_.toBigInt())); + let r = x.sub(q.mul(y_)).seal(); + + // q, r being 16 bits is enough for them to be 8 bits, + // thanks to the === x check and the r < y check below + RangeCheck.rangeCheck16(q); + RangeCheck.rangeCheck16(r); + + let remainder = UInt8.Unsafe.fromField(r); + let quotient = UInt8.Unsafe.fromField(q); + + remainder.assertLessThan(y); + return { quotient, remainder }; + } + + /** + * Check if this {@link UInt8} is less than or equal to another {@link UInt8} value. + * Returns a {@link Bool}. + * + * @example + * ```ts + * UInt8.from(3).lessThanOrEqual(UInt8.from(5)); + * ``` + */ + lessThanOrEqual(y: UInt8 | bigint | number): Bool { + let y_ = UInt8.from(y); + if (this.value.isConstant() && y_.value.isConstant()) { + return Bool(this.toBigInt() <= y_.toBigInt()); + } + return lessThanOrEqualGeneric( + this.value, + y_.value, + 1n << 8n, + RangeCheck.rangeCheck8 + ); + } + + /** + * Check if this {@link UInt8} is less than another {@link UInt8} value. + * Returns a {@link Bool}. + * + * @example + * ```ts + * UInt8.from(2).lessThan(UInt8.from(3)); + * ``` + */ + lessThan(y: UInt8 | bigint | number): Bool { + let y_ = UInt8.from(y); + if (this.value.isConstant() && y_.value.isConstant()) { + return Bool(this.toBigInt() < y_.toBigInt()); + } + return lessThanGeneric( + this.value, + y_.value, + 1n << 8n, + RangeCheck.rangeCheck8 + ); + } + + /** + * Assert that this {@link UInt8} is less than another {@link UInt8} value. + * + * **Important**: If an assertion fails, the code throws an error. + * + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertLessThan(y: UInt8 | bigint | number, message?: string) { + let y_ = UInt8.from(y); + if (this.value.isConstant() && y_.value.isConstant()) { + let [x0, y0] = [this.value.toBigInt(), y_.value.toBigInt()]; + return assert( + x0 < y0, + message ?? `UInt8.assertLessThan: expected ${x0} < ${y0}` + ); + } + try { + // 2^16 < p - 2^8, so we satisfy the assumption of `assertLessThanGeneric` + assertLessThanGeneric(this.value, y_.value, RangeCheck.rangeCheck16); + } catch (err) { + throw withMessage(err, message); + } + } + + /** + * Assert that this {@link UInt8} is less than or equal to another {@link UInt8} value. + * + * **Important**: If an assertion fails, the code throws an error. + * + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertLessThanOrEqual(y: UInt8 | bigint | number, message?: string) { + let y_ = UInt8.from(y); + if (this.value.isConstant() && y_.value.isConstant()) { + let [x0, y0] = [this.value.toBigInt(), y_.value.toBigInt()]; + return assert( + x0 <= y0, + message ?? `UInt8.assertLessThanOrEqual: expected ${x0} <= ${y0}` + ); + } + try { + // 2^16 < p - 2^8, so we satisfy the assumption of `assertLessThanOrEqualGeneric` + assertLessThanOrEqualGeneric( + this.value, + y_.value, + RangeCheck.rangeCheck16 + ); + } catch (err) { + throw withMessage(err, message); + } + } + + /** + * Check if this {@link UInt8} is greater than another {@link UInt8}. + * Returns a {@link Bool}. + * + * @example + * ```ts + * // 5 > 3 + * UInt8.from(5).greaterThan(3); + * ``` + */ + greaterThan(y: UInt8 | bigint | number) { + return UInt8.from(y).lessThan(this); + } + + /** + * Check if this {@link UInt8} is greater than or equal another {@link UInt8} value. + * Returns a {@link Bool}. + * + * @example + * ```ts + * // 3 >= 3 + * UInt8.from(3).greaterThanOrEqual(3); + * ``` + */ + greaterThanOrEqual(y: UInt8 | bigint | number) { + return UInt8.from(y).lessThanOrEqual(this); + } + + /** + * Assert that this {@link UInt8} is greater than another {@link UInt8} value. + * + * **Important**: If an assertion fails, the code throws an error. + * + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertGreaterThan(y: UInt8 | bigint | number, message?: string) { + UInt8.from(y).assertLessThan(this, message); + } + + /** + * Assert that this {@link UInt8} is greater than or equal to another {@link UInt8} value. + * + * **Important**: If an assertion fails, the code throws an error. + * + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertGreaterThanOrEqual(y: UInt8, message?: string) { + UInt8.from(y).assertLessThanOrEqual(this, message); + } + + /** + * Assert that this {@link UInt8} is equal another {@link UInt8} value. + * + * **Important**: If an assertion fails, the code throws an error. + * + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertEquals(y: UInt8 | bigint | number, message?: string) { + let y_ = UInt8.from(y); + this.value.assertEquals(y_.value, message); + } + + /** + * Serialize the {@link UInt8} to a string, e.g. for printing. + * + * **Warning**: This operation is not provable. + */ + toString() { + return this.value.toString(); + } + + /** + * Serialize the {@link UInt8} to a number. + * + * **Warning**: This operation is not provable. + */ + toNumber() { + return Number(this.value.toBigInt()); + } + + /** + * Serialize the {@link UInt8} to a bigint. + * + * **Warning**: This operation is not provable. + */ + toBigInt() { + return this.value.toBigInt(); + } + + /** + * {@link Provable.check} for {@link UInt8}. + * Proves that the input is in the [0, 255] range. + */ + static check(x: { value: Field } | Field) { + if (x instanceof Field) x = { value: x }; + RangeCheck.rangeCheck8(x.value); + } + + static toInput(x: { value: Field }): HashInput { + return { packed: [[x.value, 8]] }; + } + + /** + * Turns a {@link UInt8} into a {@link UInt32}. + */ + toUInt32(): UInt32 { + return new UInt32(this.value.value); + } + + /** + * Turns a {@link UInt8} into a {@link UInt64}. + */ + toUInt64(): UInt64 { + return new UInt64(this.value.value); + } + + /** + * Creates a {@link UInt8} with a value of 255. + */ + static MAXINT() { + return new UInt8((1n << BigInt(UInt8.NUM_BITS)) - 1n); + } + + /** + * Creates a new {@link UInt8}. + */ + static from(x: UInt8 | UInt64 | UInt32 | Field | number | bigint) { + if (x instanceof UInt8) return x; + if (x instanceof UInt64 || x instanceof UInt32 || x instanceof Field) { + // if the input could be larger than 8 bits, we have to prove that it is not + let xx = x instanceof Field ? { value: x } : x; + UInt8.check(xx); + return new UInt8(xx.value.value); + } + return new UInt8(x); + } + + private static checkConstant(x: Field) { + if (!x.isConstant()) return; + RangeCheck.rangeCheck8(x); + } +} diff --git a/src/lib/provable/merkle-list.ts b/src/lib/provable/merkle-list.ts new file mode 100644 index 0000000000..2286caee40 --- /dev/null +++ b/src/lib/provable/merkle-list.ts @@ -0,0 +1,543 @@ +import { Bool, Field } from './wrapped.js'; +import { Provable } from './provable.js'; +import { Struct } from './types/struct.js'; +import { assert } from './gadgets/common.js'; +import { provableFromClass } from './types/provable-derivers.js'; +import { Poseidon, packToFields, ProvableHashable } from './crypto/poseidon.js'; +import { Unconstrained } from './types/unconstrained.js'; + +export { + MerkleListBase, + MerkleList, + MerkleListIteratorBase, + MerkleListIterator, + WithHash, + emptyHash, + genericHash, + merkleListHash, + withHashes, +}; + +// common base types for both MerkleList and MerkleListIterator + +const emptyHash = Field(0); + +type WithHash = { previousHash: Field; element: T }; + +function WithHash(type: ProvableHashable): ProvableHashable> { + return Struct({ previousHash: Field, element: type }); +} +function toConstant(type: Provable, node: WithHash): WithHash { + return { + previousHash: node.previousHash.toConstant(), + element: Provable.toConstant(type, node.element), + }; +} + +/** + * Common base type for {@link MerkleList} and {@link MerkleListIterator} + */ +type MerkleListBase = { + hash: Field; + data: Unconstrained[]>; +}; + +function MerkleListBase(): ProvableHashable> { + return class extends Struct({ hash: Field, data: Unconstrained.provable }) { + static empty(): MerkleListBase { + return { hash: emptyHash, data: Unconstrained.from([]) }; + } + }; +} + +// merkle list + +/** + * Dynamic-length list which is represented as a single hash + * + * Supported operations are {@link push()} and {@link pop()} and some variants thereof. + * + * + * A Merkle list is generic over its element types, so before using it you must create a subclass for your element type: + * + * ```ts + * class MyList extends MerkleList.create(MyType) {} + * + * // now use it + * let list = MyList.empty(); + * + * list.push(new MyType(...)); + * + * let element = list.pop(); + * ``` + * + * Internal detail: `push()` adds elements to the _start_ of the internal array and `pop()` removes them from the start. + * This is so that the hash which represents the list is consistent with {@link MerkleListIterator}, + * and so a `MerkleList` can be used as input to `MerkleListIterator.startIterating(list)` + * (which will then iterate starting from the last pushed element). + */ +class MerkleList implements MerkleListBase { + hash: Field; + data: Unconstrained[]>; + + constructor({ hash, data }: MerkleListBase) { + this.hash = hash; + this.data = data; + } + + isEmpty() { + return this.hash.equals(this.Constructor.emptyHash); + } + + /** + * Push a new element to the list. + */ + push(element: T) { + let previousHash = this.hash; + this.hash = this.nextHash(previousHash, element); + this.data.updateAsProver((data) => [ + toConstant(this.innerProvable, { previousHash, element }), + ...data, + ]); + } + + /** + * Push a new element to the list, if the `condition` is true. + */ + pushIf(condition: Bool, element: T) { + let previousHash = this.hash; + this.hash = Provable.if( + condition, + this.nextHash(previousHash, element), + previousHash + ); + this.data.updateAsProver((data) => + condition.toBoolean() + ? [toConstant(this.innerProvable, { previousHash, element }), ...data] + : data + ); + } + + private popWitness() { + return Provable.witness(WithHash(this.innerProvable), () => { + let [value, ...data] = this.data.get(); + let head = value ?? { + previousHash: this.Constructor.emptyHash, + element: this.innerProvable.empty(), + }; + this.data.set(data); + return head; + }); + } + + /** + * Remove the last element from the list and return it. + * + * This proves that the list is non-empty, and fails otherwise. + */ + popExn(): T { + let { previousHash, element } = this.popWitness(); + + let currentHash = this.nextHash(previousHash, element); + this.hash.assertEquals(currentHash); + + this.hash = previousHash; + return element; + } + + /** + * Remove the last element from the list and return it. + * + * If the list is empty, returns a dummy element. + */ + pop(): T { + let { previousHash, element } = this.popWitness(); + let isEmpty = this.isEmpty(); + let emptyHash = this.Constructor.emptyHash; + + let currentHash = this.nextHash(previousHash, element); + currentHash = Provable.if(isEmpty, emptyHash, currentHash); + this.hash.assertEquals(currentHash); + + this.hash = Provable.if(isEmpty, emptyHash, previousHash); + let provable = this.innerProvable; + return Provable.if(isEmpty, provable, provable.empty(), element); + } + + /** + * Return the last element, but only remove it if `condition` is true. + * + * If the list is empty, returns a dummy element. + */ + popIf(condition: Bool) { + let originalHash = this.hash; + let element = this.pop(); + + // if the condition is false, we restore the original state + this.data.updateAsProver((data) => { + let node = { previousHash: this.hash, element }; + return condition.toBoolean() + ? data + : [toConstant(this.innerProvable, node), ...data]; + }); + this.hash = Provable.if(condition, this.hash, originalHash); + + return element; + } + + clone(): MerkleList { + let data = Unconstrained.witness(() => [...this.data.get()]); + return new this.Constructor({ hash: this.hash, data }); + } + + startIterating(): MerkleListIterator { + let merkleArray = MerkleListIterator.createFromList(this.Constructor); + return merkleArray.startIterating(this); + } + + /** + * Create a Merkle list type + * + * Optionally, you can tell `create()` how to do the hash that pushes a new list element, by passing a `nextHash` function. + * + * @example + * ```ts + * class MyList extends MerkleList.create(Field, (hash, x) => + * Poseidon.hashWithPrefix('custom', [hash, x]) + * ) {} + * ``` + */ + static create( + type: ProvableHashable, + nextHash: (hash: Field, value: T) => Field = merkleListHash(type), + emptyHash_ = emptyHash + ): typeof MerkleList & { + // override static methods with strict types + empty: () => MerkleList; + from: (array: T[]) => MerkleList; + provable: ProvableHashable>; + } { + class MerkleListTBase extends MerkleList { + static _innerProvable = type; + + static _provable = provableFromClass(MerkleListTBase, { + hash: Field, + data: Unconstrained.provable, + }) as ProvableHashable>; + + static _nextHash = nextHash; + static _emptyHash = emptyHash_; + + static empty(): MerkleList { + return new this({ hash: emptyHash_, data: Unconstrained.from([]) }); + } + + static from(array: T[]): MerkleList { + let { hash, data } = withHashes(array, nextHash, emptyHash_); + let unconstrained = Unconstrained.witness(() => + data.map((x) => toConstant(type, x)) + ); + return new this({ data: unconstrained, hash }); + } + + static get provable(): ProvableHashable> { + assert(this._provable !== undefined, 'MerkleList not initialized'); + return this._provable; + } + } + // override `instanceof` for subclasses + return class MerkleListT extends MerkleListTBase { + static [Symbol.hasInstance](x: any): boolean { + return x instanceof MerkleListTBase; + } + }; + } + + // dynamic subclassing infra + static _nextHash: ((hash: Field, t: any) => Field) | undefined; + static _emptyHash: Field | undefined; + + static _provable: ProvableHashable> | undefined; + static _innerProvable: ProvableHashable | undefined; + + get Constructor() { + return this.constructor as typeof MerkleList; + } + + nextHash(hash: Field, value: T): Field { + assert( + this.Constructor._nextHash !== undefined, + 'MerkleList not initialized' + ); + return this.Constructor._nextHash(hash, value); + } + + static get emptyHash() { + assert(this._emptyHash !== undefined, 'MerkleList not initialized'); + return this._emptyHash; + } + + get innerProvable(): ProvableHashable { + assert( + this.Constructor._innerProvable !== undefined, + 'MerkleList not initialized' + ); + return this.Constructor._innerProvable; + } +} + +// merkle list iterator + +type MerkleListIteratorBase = { + readonly hash: Field; + readonly data: Unconstrained[]>; + + /** + * The merkle list hash of `[data[currentIndex], ..., data[length-1]]` (when hashing from right to left). + * + * For example: + * - If `currentIndex === 0`, then `currentHash === this.hash` is the hash of the entire array. + * - If `currentIndex === length`, then `currentHash === emptyHash` is the hash of an empty array. + */ + currentHash: Field; + /** + * The index of the element that will be returned by the next call to `next()`. + */ + currentIndex: Unconstrained; +}; + +/** + * MerkleListIterator helps iterating through a Merkle list. + * This works similar to calling `list.pop()` repeatedly, but maintaining the entire list instead of removing elements. + * + * The core method that supports iteration is {@link next()}. + * + * ```ts + * let iterator = MerkleListIterator.startIterating(list); + * + * let firstElement = iterator.next(); + * ``` + * + * We maintain two commitments, both of which are equivalent to a Merkle list hash starting _from the end_ of the array: + * - One to the entire array, to prove that we start iterating at the beginning. + * - One to the array from the current index until the end, to efficiently step forward. + */ +class MerkleListIterator implements MerkleListIteratorBase { + // fixed parts + readonly data: Unconstrained[]>; + readonly hash: Field; + + // mutable parts + currentHash: Field; + currentIndex: Unconstrained; + + constructor(value: MerkleListIteratorBase) { + Object.assign(this, value); + } + + assertAtStart() { + return this.currentHash.assertEquals(this.hash); + } + + isAtEnd() { + return this.currentHash.equals(this.Constructor.emptyHash); + } + jumpToEnd() { + this.currentIndex.setTo( + Unconstrained.witness(() => this.data.get().length) + ); + this.currentHash = this.Constructor.emptyHash; + } + jumpToEndIf(condition: Bool) { + Provable.asProver(() => { + if (condition.toBoolean()) { + this.currentIndex.set(this.data.get().length); + } + }); + this.currentHash = Provable.if( + condition, + this.Constructor.emptyHash, + this.currentHash + ); + } + + next() { + // next corresponds to `pop()` in MerkleList + // it returns a dummy element if we're at the end of the array + let { previousHash, element } = Provable.witness( + WithHash(this.innerProvable), + () => + this.data.get()[this.currentIndex.get()] ?? { + previousHash: this.Constructor.emptyHash, + element: this.innerProvable.empty(), + } + ); + + let isDummy = this.isAtEnd(); + let emptyHash = this.Constructor.emptyHash; + let correctHash = this.nextHash(previousHash, element); + let requiredHash = Provable.if(isDummy, emptyHash, correctHash); + this.currentHash.assertEquals(requiredHash); + + this.currentIndex.updateAsProver((i) => + Math.min(i + 1, this.data.get().length) + ); + this.currentHash = Provable.if(isDummy, emptyHash, previousHash); + + return Provable.if( + isDummy, + this.innerProvable, + this.innerProvable.empty(), + element + ); + } + + clone(): MerkleListIterator { + let data = Unconstrained.witness(() => [...this.data.get()]); + let currentIndex = Unconstrained.witness(() => this.currentIndex.get()); + return new this.Constructor({ + data, + hash: this.hash, + currentHash: this.currentHash, + currentIndex, + }); + } + + /** + * Create a Merkle array type + */ + static create( + type: ProvableHashable, + nextHash: (hash: Field, value: T) => Field = merkleListHash(type), + emptyHash_ = emptyHash + ): typeof MerkleListIterator & { + from: (array: T[]) => MerkleListIterator; + startIterating: (list: MerkleListBase) => MerkleListIterator; + empty: () => MerkleListIterator; + provable: ProvableHashable>; + } { + return class Iterator extends MerkleListIterator { + static _innerProvable = type; + + static _provable = provableFromClass(Iterator, { + hash: Field, + data: Unconstrained.provable, + currentHash: Field, + currentIndex: Unconstrained.provable, + }) satisfies ProvableHashable> as ProvableHashable< + MerkleListIterator + >; + + static _nextHash = nextHash; + static _emptyHash = emptyHash_; + + static from(array: T[]): MerkleListIterator { + let { hash, data } = withHashes(array, nextHash, emptyHash_); + let unconstrained = Unconstrained.witness(() => + data.map((x) => toConstant(type, x)) + ); + return this.startIterating({ data: unconstrained, hash }); + } + + static startIterating({ + data, + hash, + }: MerkleListBase): MerkleListIterator { + return new this({ + data, + hash, + currentHash: hash, + currentIndex: Unconstrained.from(0), + }); + } + + static empty(): MerkleListIterator { + return this.from([]); + } + + static get provable(): ProvableHashable> { + assert( + this._provable !== undefined, + 'MerkleListIterator not initialized' + ); + return this._provable; + } + }; + } + + static createFromList(merkleList: typeof MerkleList) { + return this.create( + merkleList.prototype.innerProvable, + merkleList._nextHash + ); + } + + // dynamic subclassing infra + static _nextHash: ((hash: Field, value: any) => Field) | undefined; + static _emptyHash: Field | undefined; + + static _provable: ProvableHashable> | undefined; + static _innerProvable: ProvableHashable | undefined; + + get Constructor() { + return this.constructor as typeof MerkleListIterator; + } + + nextHash(hash: Field, value: T): Field { + assert( + this.Constructor._nextHash !== undefined, + 'MerkleListIterator not initialized' + ); + return this.Constructor._nextHash(hash, value); + } + + static get emptyHash() { + assert(this._emptyHash !== undefined, 'MerkleList not initialized'); + return this._emptyHash; + } + + get innerProvable(): ProvableHashable { + assert( + this.Constructor._innerProvable !== undefined, + 'MerkleListIterator not initialized' + ); + return this.Constructor._innerProvable; + } +} + +// hash helpers + +function genericHash( + provable: ProvableHashable, + prefix: string, + value: T +) { + let input = provable.toInput(value); + let packed = packToFields(input); + return Poseidon.hashWithPrefix(prefix, packed); +} + +function merkleListHash(provable: ProvableHashable, prefix = '') { + return function nextHash(hash: Field, value: T) { + let input = provable.toInput(value); + let packed = packToFields(input); + return Poseidon.hashWithPrefix(prefix, [hash, ...packed]); + }; +} + +function withHashes( + data: T[], + nextHash: (hash: Field, value: T) => Field, + emptyHash: Field +): { data: WithHash[]; hash: Field } { + let n = data.length; + let arrayWithHashes = Array>(n); + let currentHash = emptyHash; + + for (let i = n - 1; i >= 0; i--) { + arrayWithHashes[i] = { previousHash: currentHash, element: data[i] }; + currentHash = nextHash(currentHash, data[i]); + } + + return { data: arrayWithHashes, hash: currentHash }; +} diff --git a/src/lib/merkle_map.ts b/src/lib/provable/merkle-map.ts similarity index 93% rename from src/lib/merkle_map.ts rename to src/lib/provable/merkle-map.ts index 468018b80e..9436ed0340 100644 --- a/src/lib/merkle_map.ts +++ b/src/lib/provable/merkle-map.ts @@ -1,7 +1,7 @@ -import { arrayProp, CircuitValue } from './circuit_value.js'; -import { Field, Bool } from './core.js'; -import { Poseidon } from './hash.js'; -import { MerkleTree, MerkleWitness } from './merkle_tree.js'; +import { arrayProp, CircuitValue } from './types/circuit-value.js'; +import { Field, Bool } from './wrapped.js'; +import { Poseidon } from './crypto/poseidon.js'; +import { MerkleTree, MerkleWitness } from './merkle-tree.js'; import { Provable } from './provable.js'; const bits = 255; @@ -53,7 +53,7 @@ export class MerkleMap { /** * Sets a key of the merkle map to a given value. * @param key The key to set in the map. - * @param key The value to set. + * @param value The value to set. */ set(key: Field, value: Field) { const index = this._keyToIndex(key); diff --git a/src/lib/merkle_tree.ts b/src/lib/provable/merkle-tree.ts similarity index 87% rename from src/lib/merkle_tree.ts rename to src/lib/provable/merkle-tree.ts index a955814227..69236a3fda 100644 --- a/src/lib/merkle_tree.ts +++ b/src/lib/provable/merkle-tree.ts @@ -1,18 +1,17 @@ /** - * This file contains all code related to the [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree) implementation available in SnarkyJS. + * This file contains all code related to the [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree) implementation available in o1js. */ -import { CircuitValue, arrayProp } from './circuit_value.js'; -import { Circuit } from './circuit.js'; -import { Poseidon } from './hash.js'; -import { Bool, Field } from './core.js'; +import { CircuitValue, arrayProp } from './types/circuit-value.js'; +import { Poseidon } from './crypto/poseidon.js'; +import { Bool, Field } from './wrapped.js'; import { Provable } from './provable.js'; // external API export { Witness, MerkleTree, MerkleWitness, BaseMerkleWitness }; // internal API -export { maybeSwap, maybeSwapBad }; +export { maybeSwap }; type Witness = { isLeft: boolean; sibling: Field }[]; @@ -38,7 +37,7 @@ class MerkleTree { constructor(public readonly height: number) { this.zeroes = new Array(height); this.zeroes[0] = Field(0); - for (let i = 1; i < height; i+=1) { + for (let i = 1; i < height; i += 1) { this.zeroes[i] = Poseidon.hash([this.zeroes[i - 1], this.zeroes[i - 1]]); } } @@ -195,23 +194,6 @@ class BaseMerkleWitness extends CircuitValue { return hash; } - /** - * Calculates a root depending on the leaf value. - * @deprecated This is a less efficient version of {@link calculateRoot} which was added for compatibility with existing deployed contracts - */ - calculateRootSlow(leaf: Field): Field { - let hash = leaf; - let n = this.height(); - - for (let i = 1; i < n; ++i) { - let isLeft = this.isLeft[i - 1]; - const [left, right] = maybeSwapBad(isLeft, hash, this.path[i - 1]); - hash = Poseidon.hash([left, right]); - } - - return hash; - } - /** * Calculates the index of the leaf node that belongs to this Witness. * @returns Index of the leaf. @@ -244,13 +226,7 @@ function MerkleWitness(height: number): typeof BaseMerkleWitness { return MerkleWitness_; } -function maybeSwapBad(b: Bool, x: Field, y: Field): [Field, Field] { - const x_ = Provable.if(b, x, y); // y + b*(x - y) - const y_ = Provable.if(b, y, x); // x + b*(y - x) - return [x_, y_]; -} - -// more efficient version of `maybeSwapBad` which reuses an intermediate variable +// more efficient than 2x `Provable.if()` by reusing an intermediate variable function maybeSwap(b: Bool, x: Field, y: Field): [Field, Field] { let m = b.toField().mul(x.sub(y)); // b*(x - y) const x_ = y.add(m); // y + b*(x - y) diff --git a/src/lib/provable/packed.ts b/src/lib/provable/packed.ts new file mode 100644 index 0000000000..6bf7d86908 --- /dev/null +++ b/src/lib/provable/packed.ts @@ -0,0 +1,268 @@ +import { provableFromClass } from './types/provable-derivers.js'; +import { HashInput, ProvableExtended } from './types/struct.js'; +import { Unconstrained } from './types/unconstrained.js'; +import { Field } from './field.js'; +import { assert } from './gadgets/common.js'; +import { Poseidon, ProvableHashable, packToFields } from './crypto/poseidon.js'; +import { Provable } from './provable.js'; +import { fields, modifiedField } from './types/fields.js'; + +export { Packed, Hashed }; + +/** + * `Packed` is a "packed" representation of any type `T`. + * + * "Packed" means that field elements which take up fewer than 254 bits are packed together into + * as few field elements as possible. + * + * For example, you can pack several Bools (1 bit) or UInt32s (32 bits) into a single field element. + * + * Using a packed representation can make sense in provable code where the number of constraints + * depends on the number of field elements per value. + * + * For example, `Provable.if(bool, x, y)` takes O(n) constraints, where n is the number of field + * elements in x and y. + * + * Usage: + * + * ```ts + * // define a packed type from a type + * let PackedType = Packed.create(MyType); + * + * // pack a value + * let packed = PackedType.pack(value); + * + * // ... operations on packed values, more efficient than on plain values ... + * + * // unpack a value + * let value = packed.unpack(); + * ``` + * + * **Warning**: Packing only makes sense where packing actually reduces the number of field elements. + * For example, it doesn't make sense to pack a _single_ Bool, because it will be 1 field element before + * and after packing. On the other hand, it does makes sense to pack a type that holds 10 or 20 Bools. + */ +class Packed { + packed: Field[]; + value: Unconstrained; + + /** + * Create a packed representation of `type`. You can then use `PackedType.pack(x)` to pack a value. + */ + static create(type: ProvableExtended): typeof Packed & { + provable: ProvableHashable>; + } { + // compute size of packed representation + let input = type.toInput(type.empty()); + let packedSize = countFields(input); + + return class Packed_ extends Packed { + static _innerProvable = type; + static _provable = provableFromClass(Packed_, { + packed: fields(packedSize), + value: Unconstrained.provable, + }) as ProvableHashable>; + + static empty(): Packed { + return Packed_.pack(type.empty()); + } + + static get provable() { + assert(this._provable !== undefined, 'Packed not initialized'); + return this._provable; + } + }; + } + + constructor(packed: Field[], value: Unconstrained) { + this.packed = packed; + this.value = value; + } + + /** + * Pack a value. + */ + static pack(x: T): Packed { + let type = this.innerProvable; + let input = type.toInput(x); + let packed = packToFields(input); + let unconstrained = Unconstrained.witness(() => + Provable.toConstant(type, x) + ); + return new this(packed, unconstrained); + } + + /** + * Unpack a value. + */ + unpack(): T { + let value = Provable.witness(this.Constructor.innerProvable, () => + this.value.get() + ); + + // prove that the value packs to the packed fields + let input = this.Constructor.innerProvable.toInput(value); + let packed = packToFields(input); + for (let i = 0; i < this.packed.length; i++) { + this.packed[i].assertEquals(packed[i]); + } + + return value; + } + + toFields(): Field[] { + return this.packed; + } + + // dynamic subclassing infra + static _provable: ProvableHashable> | undefined; + static _innerProvable: ProvableExtended | undefined; + + get Constructor(): typeof Packed { + return this.constructor as typeof Packed; + } + + static get innerProvable(): ProvableExtended { + assert(this._innerProvable !== undefined, 'Packed not initialized'); + return this._innerProvable; + } +} + +function countFields(input: HashInput) { + let n = input.fields?.length ?? 0; + let pendingBits = 0; + + for (let [, bits] of input.packed ?? []) { + pendingBits += bits; + if (pendingBits >= Field.sizeInBits) { + n++; + pendingBits = bits; + } + } + if (pendingBits > 0) n++; + + return n; +} + +/** + * `Hashed` represents a type `T` by its hash. + * + * Since a hash is only a single field element, this can be more efficient in provable code + * where the number of constraints depends on the number of field elements per value. + * + * For example, `Provable.if(bool, x, y)` takes O(n) constraints, where n is the number of field + * elements in x and y. With Hashed, this is reduced to O(1). + * + * The downside is that you will pay the overhead of hashing your values, so it helps to experiment + * in which parts of your code a hashed representation is beneficial. + * + * Usage: + * + * ```ts + * // define a hashed type from a type + * let HashedType = Hashed.create(MyType); + * + * // hash a value + * let hashed = HashedType.hash(value); + * + * // ... operations on hashes, more efficient than on plain values ... + * + * // unhash to get the original value + * let value = hashed.unhash(); + * ``` + */ +class Hashed { + hash: Field; + value: Unconstrained; + + /** + * Create a hashed representation of `type`. You can then use `HashedType.hash(x)` to wrap a value in a `Hashed`. + */ + static create( + type: ProvableHashable, + hash?: (t: T) => Field + ): typeof Hashed & { + provable: ProvableHashable>; + } { + let _hash = hash ?? ((t: T) => Poseidon.hashPacked(type, t)); + + let dummyHash = _hash(type.empty()); + + return class Hashed_ extends Hashed { + static _innerProvable = type; + static _provable = provableFromClass(Hashed_, { + hash: modifiedField({ empty: () => dummyHash }), + value: Unconstrained.provable, + }) as ProvableHashable>; + + static _hash = _hash satisfies (t: T) => Field; + + static empty(): Hashed { + return new this(dummyHash, Unconstrained.from(type.empty())); + } + + static get provable() { + assert(this._provable !== undefined, 'Hashed not initialized'); + return this._provable; + } + }; + } + + constructor(hash: Field, value: Unconstrained) { + this.hash = hash; + this.value = value; + } + + static _hash(_: any): Field { + assert(false, 'Hashed not initialized'); + } + + /** + * Wrap a value, and represent it by its hash in provable code. + * + * ```ts + * let hashed = HashedType.hash(value); + * ``` + * + * Optionally, if you already have the hash, you can pass it in and avoid recomputing it. + */ + static hash(value: T, hash?: Field): Hashed { + hash ??= this._hash(value); + let unconstrained = Unconstrained.witness(() => + Provable.toConstant(this.innerProvable, value) + ); + return new this(hash, unconstrained); + } + + /** + * Unwrap a value from its hashed variant. + */ + unhash(): T { + let value = Provable.witness(this.Constructor.innerProvable, () => + this.value.get() + ); + + // prove that the value hashes to the hash + let hash = this.Constructor._hash(value); + this.hash.assertEquals(hash); + + return value; + } + + toFields(): Field[] { + return [this.hash]; + } + + // dynamic subclassing infra + static _provable: ProvableHashable> | undefined; + static _innerProvable: ProvableHashable | undefined; + + get Constructor(): typeof Hashed { + return this.constructor as typeof Hashed; + } + + static get innerProvable(): ProvableHashable { + assert(this._innerProvable !== undefined, 'Hashed not initialized'); + return this._innerProvable; + } +} diff --git a/src/lib/provable.ts b/src/lib/provable/provable.ts similarity index 85% rename from src/lib/provable.ts rename to src/lib/provable/provable.ts index 4bc079b5ce..20c4bb6f01 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable/provable.ts @@ -3,27 +3,25 @@ * - a namespace with tools for writing provable code * - the main interface for types that can be used in provable code */ -import { Field, Bool } from './core.js'; -import { Provable as Provable_, Snarky } from '../snarky.js'; -import type { FlexibleProvable, ProvableExtended } from './circuit_value.js'; -import { Context } from './global-context.js'; +import { Bool } from './bool.js'; +import { Field } from './field.js'; +import type { Provable as Provable_ } from './types/provable-intf.js'; +import type { FlexibleProvable, ProvableExtended } from './types/struct.js'; +import { Context } from '../util/global-context.js'; import { HashInput, InferJson, InferProvable, InferredProvable, -} from '../bindings/lib/provable-snarky.js'; -import { isField } from './field.js'; +} from './types/provable-derivers.js'; import { inCheckedComputation, inProver, - snarkContext, asProver, - runAndCheck, - runUnchecked, constraintSystem, -} from './provable-context.js'; -import { isBool } from './bool.js'; + generateWitness, +} from './core/provable-context.js'; +import { witness, witnessAsync } from './types/witness.js'; // external API export { Provable }; @@ -36,11 +34,17 @@ export { getBlindingValue, }; -// TODO move type declaration here /** - * `Provable` is the general circuit type interface. It describes how a type `T` is made up of field elements and auxiliary (non-field element) data. + * `Provable` is the general interface for provable types in o1js. * - * You will find this as the required input type in a few places in SnarkyJS. One convenient way to create a `Provable` is using `Struct`. + * `Provable` describes how a type `T` is made up of {@link Field} elements and "auxiliary" (non-provable) data. + * + * `Provable` is the required input type in several methods in o1js. + * One convenient way to create a `Provable` is using `Struct`. + * + * All built-in provable types in o1js ({@link Field}, {@link Bool}, etc.) are instances of `Provable` as well. + * + * Note: These methods are meant to be used by the library internally and are not directly when writing provable code. */ type Provable = Provable_; @@ -63,6 +67,12 @@ const Provable = { * ``` */ witness, + /** + * Create a new witness from an async callback. + * + * See {@link Provable.witness} for more information. + */ + witnessAsync, /** * Proof-compatible if-statement. * This behaves like a ternary conditional statement in JS. @@ -119,6 +129,17 @@ const Provable = { * ``` */ Array: provableArray, + /** + * Check whether a value is constant. + * See {@link FieldVar} for more information about constants and variables. + * + * @example + * ```ts + * let x = Field(42); + * Provable.isConstant(Field, x); // true + * ``` + */ + isConstant, /** * Interface to log elements within a circuit. Similar to `console.log()`. * @example @@ -142,27 +163,31 @@ const Provable = { * Runs provable code quickly, without creating a proof, but still checking whether constraints are satisfied. * @example * ```ts - * Provable.runAndCheck(() => { + * await Provable.runAndCheck(() => { * // Your code to check here * }); * ``` */ - runAndCheck, + async runAndCheck(f: (() => Promise) | (() => void)) { + await generateWitness(f, { checkConstraints: true }); + }, /** * Runs provable code quickly, without creating a proof, and not checking whether constraints are satisfied. * @example * ```ts - * Provable.runUnchecked(() => { + * await Provable.runUnchecked(() => { * // Your code to run here * }); * ``` */ - runUnchecked, + async runUnchecked(f: (() => Promise) | (() => void)) { + await generateWitness(f, { checkConstraints: false }); + }, /** * Returns information about the constraints created by the callback function. * @example * ```ts - * const result = Provable.constraintSystem(circuit); + * const result = await Provable.constraintSystem(circuit); * console.log(result); * ``` */ @@ -187,53 +212,17 @@ const Provable = { * ``` */ inCheckedComputation, -}; - -function witness = FlexibleProvable>( - type: S, - compute: () => T -): T { - let ctx = snarkContext.get(); - - // outside provable code, we just call the callback and return its cloned result - if (!inCheckedComputation() || ctx.inWitnessBlock) { - return clone(type, compute()); - } - let proverValue: T | undefined = undefined; - let fields: Field[]; - - let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); - try { - let [, ...fieldVars] = Snarky.exists(type.sizeInFields(), () => { - proverValue = compute(); - let fields = type.toFields(proverValue); - let fieldConstants = fields.map((x) => x.toConstant().value[1]); - - // TODO: enable this check - // currently it throws for Scalar.. which seems to be flexible about what length is returned by toFields - // if (fields.length !== type.sizeInFields()) { - // throw Error( - // `Invalid witness. Expected ${type.sizeInFields()} field elements, got ${ - // fields.length - // }.` - // ); - // } - return [0, ...fieldConstants]; - }); - fields = fieldVars.map(Field); - } finally { - snarkContext.leave(id); - } - // rebuild the value from its fields (which are now variables) and aux data - let aux = type.toAuxiliary(proverValue); - let value = (type as Provable).fromFields(fields, aux); - - // add type-specific constraints - type.check(value); - - return value; -} + /** + * Returns a constant version of a provable type. + */ + toConstant(type: Provable, value: T) { + return type.fromFields( + type.toFields(value).map((x) => x.toConstant()), + type.toAuxiliary(value) + ); + }, +}; type ToFieldable = { toFields(): Field[] }; @@ -332,13 +321,7 @@ function ifImplicit(condition: Bool, x: T, y: T): T { `If x, y are Structs or other custom types, you can use the following:\n` + `Provable.if(bool, MyType, x, y)` ); - // TODO remove second condition once we have consolidated field class back into one - // if (type !== y.constructor) { - if ( - type !== y.constructor && - !(isField(x) && isField(y)) && - !(isBool(x) && isBool(y)) - ) { + if (type !== y.constructor) { throw Error( 'Provable.if: Mismatched argument types. Try using an explicit type argument:\n' + `Provable.if(bool, MyType, x, y)` @@ -375,7 +358,7 @@ function switch_>( if (mask.every((b) => b.toField().isConstant())) checkMask(); else Provable.asProver(checkMask); let size = type.sizeInFields(); - let fields = Array(size).fill(Field(0)); + let fields = Array(size).fill(new Field(0)); for (let i = 0; i < nValues; i++) { let valueFields = type.toFields(values[i]); let maskField = mask[i].toField(); @@ -392,6 +375,10 @@ function switch_>( return (type as Provable).fromFields(fields, aux); } +function isConstant(type: Provable, x: T): boolean { + return type.toFields(x).every((x) => x.isConstant()); +} + // logging in provable code function log(...args: any) { @@ -566,5 +553,12 @@ function provableArray>( HashInput.empty ); }, + + empty() { + if (!('empty' in type)) { + throw Error('circuitArray.empty: element type has no empty() method'); + } + return Array.from({ length }, () => type.empty()); + }, } satisfies ProvableExtended as any; } diff --git a/src/lib/scalar.ts b/src/lib/provable/scalar.ts similarity index 75% rename from src/lib/scalar.ts rename to src/lib/provable/scalar.ts index dcef9e20d1..87b9f1ce76 100644 --- a/src/lib/scalar.ts +++ b/src/lib/provable/scalar.ts @@ -1,7 +1,9 @@ -import { Snarky, Provable } from '../snarky.js'; -import { Scalar as Fq } from '../provable/curve-bigint.js'; -import { Field, FieldConst, FieldVar } from './field.js'; -import { MlArray } from './ml/base.js'; +import { Snarky } from '../../snarky.js'; +import { Fq } from '../../bindings/crypto/finite-field.js'; +import { Scalar as SignableFq } from '../../mina-signer/src/curve-bigint.js'; +import { Field } from './field.js'; +import { FieldVar, FieldConst } from './core/fieldvar.js'; +import { MlArray } from '../ml/base.js'; import { Bool } from './bool.js'; export { Scalar, ScalarConst, unshift, shift }; @@ -10,14 +12,17 @@ export { Scalar, ScalarConst, unshift, shift }; export { constantScalarToBigint }; type BoolVar = FieldVar; -type ScalarConst = Uint8Array; +type ScalarConst = [0, bigint]; const ScalarConst = { fromBigint: constFromBigint, toBigint: constToBigint, + is(x: any): x is ScalarConst { + return Array.isArray(x) && x[0] === 0 && typeof x[1] === 'bigint'; + }, }; -let scalarShift = Fq(1n + 2n ** 255n); +let scalarShift = Fq.mod(1n + 2n ** 255n); let oneHalf = Fq.inverse(2n)!; type ConstantScalar = Scalar & { constantValue: ScalarConst }; @@ -31,7 +36,7 @@ class Scalar { static ORDER = Fq.modulus; - private constructor(bits: MlArray, constantValue?: Fq) { + private constructor(bits: MlArray, constantValue?: bigint) { this.value = bits; constantValue ??= toConstantScalar(bits); if (constantValue !== undefined) { @@ -44,10 +49,10 @@ class Scalar { * * If the input is too large, it is reduced modulo the scalar field size. */ - static from(x: Scalar | Uint8Array | bigint | number | string) { + static from(x: Scalar | ScalarConst | bigint | number | string) { if (x instanceof Scalar) return x; - if (x instanceof Uint8Array) x = ScalarConst.toBigint(x); - let scalar = Fq(x); + let x_ = ScalarConst.is(x) ? constToBigint(x) : x; + let scalar = Fq.mod(BigInt(x_)); let bits = toBits(scalar); return new Scalar(bits, scalar); } @@ -74,18 +79,11 @@ class Scalar { return new Scalar([0, ...constBits]) as ConstantScalar; } - /** - * @deprecated use {@link Scalar.from} - */ - static fromBigInt(x: bigint) { - return Scalar.from(x); - } - /** * Convert this {@link Scalar} into a bigint */ toBigInt() { - return this.#assertConstant('toBigInt'); + return assertConstant(this, 'toBigInt'); } // TODO: fix this API. we should represent "shifted status" internally and use @@ -96,7 +94,10 @@ class Scalar { * **Warning**: The bits are interpreted as the bits of 2s + 1 + 2^255, where s is the Scalar. */ static fromBits(bits: Bool[]) { - return Scalar.fromFields(bits.map((b) => b.toField())); + return Scalar.fromFields([ + ...bits.map((b) => b.toField()), + ...Array(Fq.sizeInBits - bits.length).fill(new Bool(false)), + ]); } /** @@ -109,17 +110,13 @@ class Scalar { // operations on constant scalars - #assertConstant(name: string) { - return constantScalarToBigint(this, `Scalar.${name}`); - } - /** * Negate a scalar field element. * * **Warning**: This method is not available for provable code. */ neg() { - let x = this.#assertConstant('neg'); + let x = assertConstant(this, 'neg'); let z = Fq.negate(x); return Scalar.from(z); } @@ -130,8 +127,8 @@ class Scalar { * **Warning**: This method is not available for provable code. */ add(y: Scalar) { - let x = this.#assertConstant('add'); - let y0 = y.#assertConstant('add'); + let x = assertConstant(this, 'add'); + let y0 = assertConstant(y, 'add'); let z = Fq.add(x, y0); return Scalar.from(z); } @@ -142,8 +139,8 @@ class Scalar { * **Warning**: This method is not available for provable code. */ sub(y: Scalar) { - let x = this.#assertConstant('sub'); - let y0 = y.#assertConstant('sub'); + let x = assertConstant(this, 'sub'); + let y0 = assertConstant(y, 'sub'); let z = Fq.sub(x, y0); return Scalar.from(z); } @@ -154,8 +151,8 @@ class Scalar { * **Warning**: This method is not available for provable code. */ mul(y: Scalar) { - let x = this.#assertConstant('mul'); - let y0 = y.#assertConstant('mul'); + let x = assertConstant(this, 'mul'); + let y0 = assertConstant(y, 'mul'); let z = Fq.mul(x, y0); return Scalar.from(z); } @@ -167,8 +164,8 @@ class Scalar { * **Warning**: This method is not available for provable code. */ div(y: Scalar) { - let x = this.#assertConstant('div'); - let y0 = y.#assertConstant('div'); + let x = assertConstant(this, 'div'); + let y0 = assertConstant(y, 'div'); let z = Fq.div(x, y0); if (z === undefined) throw Error('Scalar.div(): Division by zero'); return Scalar.from(z); @@ -176,11 +173,11 @@ class Scalar { // TODO don't leak 'shifting' to the user and remove these methods shift() { - let x = this.#assertConstant('shift'); + let x = assertConstant(this, 'shift'); return Scalar.from(shift(x)); } unshift() { - let x = this.#assertConstant('unshift'); + let x = assertConstant(this, 'unshift'); return Scalar.from(unshift(x)); } @@ -193,7 +190,7 @@ class Scalar { * is needed to represent all Scalars. However, for a random Scalar, the high bit will be `false` with overwhelming probability. */ toFieldsCompressed(): { field: Field; highBit: Bool } { - let s = this.#assertConstant('toFieldsCompressed'); + let s = assertConstant(this, 'toFieldsCompressed'); let lowBitSize = BigInt(Fq.sizeInBits - 1); let lowBitMask = (1n << lowBitSize) - 1n; return { @@ -234,6 +231,20 @@ class Scalar { return Scalar.toFields(this); } + /** + * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. + * + * This function is the implementation of `ProvableExtended.toInput()` for the {@link Scalar} type. + * + * @param value - The {@link Scalar} element to get the `input` array. + * + * @return An object where the `fields` key is a {@link Field} array of length 1 created from this {@link Field}. + * + */ + static toInput(x: Scalar): { packed: [Field, number][] } { + return { packed: Scalar.toFields(x).map((f) => [f, 1]) }; + } + /** * Part of the {@link Provable} interface. * @@ -289,7 +300,7 @@ class Scalar { * This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the Scalar. */ static toJSON(x: Scalar) { - let s = x.#assertConstant('toJSON'); + let s = assertConstant(x, 'toJSON'); return s.toString(); } @@ -305,11 +316,17 @@ class Scalar { * This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the Scalar. */ static fromJSON(x: string) { - return Scalar.from(Fq.fromJSON(x)); + return Scalar.from(SignableFq.fromJSON(x)); } } -function toConstantScalar([, ...bits]: MlArray): Fq | undefined { +// internal helpers + +function assertConstant(x: Scalar, name: string) { + return constantScalarToBigint(x, `Scalar.${name}`); +} + +function toConstantScalar([, ...bits]: MlArray): bigint | undefined { if (bits.length !== Fq.sizeInBits) throw Error( `Scalar: expected bits array of length ${Fq.sizeInBits}, got ${bits.length}` @@ -320,14 +337,14 @@ function toConstantScalar([, ...bits]: MlArray): Fq | undefined { if (!FieldVar.isConstant(bool)) return undefined; constantBits[i] = FieldConst.equal(bool[1], FieldConst[1]); } - let sShifted = Fq.fromBits(constantBits); + let sShifted = SignableFq.fromBits(constantBits); return shift(sShifted); } -function toBits(constantValue: Fq): MlArray { +function toBits(constantValue: bigint): MlArray { return [ 0, - ...Fq.toBits(unshift(constantValue)).map((b) => + ...SignableFq.toBits(unshift(constantValue)).map((b) => FieldVar.constant(BigInt(b)) ), ]; @@ -336,22 +353,22 @@ function toBits(constantValue: Fq): MlArray { /** * s -> 2s + 1 + 2^255 */ -function shift(s: Fq): Fq { +function shift(s: bigint) { return Fq.add(Fq.add(s, s), scalarShift); } /** * inverse of shift, 2s + 1 + 2^255 -> s */ -function unshift(s: Fq): Fq { +function unshift(s: bigint) { return Fq.mul(Fq.sub(s, scalarShift), oneHalf); } -function constToBigint(x: ScalarConst): Fq { - return Fq.fromBytes([...x]); +function constToBigint(x: ScalarConst) { + return x[1]; } -function constFromBigint(x: Fq) { - return Uint8Array.from(Fq.toBytes(x)); +function constFromBigint(x: bigint): ScalarConst { + return [0, x]; } function constantScalarToBigint(s: Scalar, name: string) { diff --git a/src/lib/string.ts b/src/lib/provable/string.ts similarity index 94% rename from src/lib/string.ts rename to src/lib/provable/string.ts index 623aa4a72e..b5a85878a8 100644 --- a/src/lib/string.ts +++ b/src/lib/provable/string.ts @@ -1,7 +1,8 @@ -import { Bool, Field } from '../lib/core.js'; -import { arrayProp, CircuitValue, prop } from './circuit_value.js'; +import { Bool, Field } from './wrapped.js'; import { Provable } from './provable.js'; -import { Poseidon } from './hash.js'; +import { Poseidon } from './crypto/poseidon.js'; +import { Gadgets } from './gadgets/gadgets.js'; +import { CircuitValue, arrayProp, prop } from './types/circuit-value.js'; export { Character, CircuitString }; @@ -31,7 +32,7 @@ class Character extends CircuitValue { // TODO: Add support for more character sets // right now it's 16 bits because 8 not supported :/ static check(c: Character) { - c.value.rangeCheckHelper(16).assertEquals(c.value); + Gadgets.rangeCheckN(16, c.value); } } @@ -101,7 +102,7 @@ class CircuitString extends CircuitValue { .slice(0, length) .concat(otherChars.slice(0, n - length)); } - // compute the actual result, by always picking the char which correponds to the actual length + // compute the actual result, by always picking the char which corresponds to the actual length let result: Character[] = []; let mask = this.lengthMask(); for (let i = 0; i < n; i++) { diff --git a/src/lib/provable/test/arithmetic.unit-test.ts b/src/lib/provable/test/arithmetic.unit-test.ts new file mode 100644 index 0000000000..579dce8236 --- /dev/null +++ b/src/lib/provable/test/arithmetic.unit-test.ts @@ -0,0 +1,59 @@ +import { ZkProgram } from '../../proof-system/zkprogram.js'; +import { + equivalentProvable as equivalent, + equivalentAsync, + field, + record, +} from '../../testing/equivalent.js'; +import { Field } from '../wrapped.js'; +import { Gadgets } from '../gadgets/gadgets.js'; +import { provable } from '../types/struct.js'; +import { assert } from '../gadgets/common.js'; + +let Arithmetic = ZkProgram({ + name: 'arithmetic', + publicOutput: provable({ + remainder: Field, + quotient: Field, + }), + methods: { + divMod32: { + privateInputs: [Field], + async method(a: Field) { + return Gadgets.divMod32(a); + }, + }, + }, +}); + +await Arithmetic.compile(); + +const divMod32Helper = (x: bigint) => { + let quotient = x >> 32n; + let remainder = x - (quotient << 32n); + return { remainder, quotient }; +}; +const divMod32Output = record({ remainder: field, quotient: field }); + +equivalent({ + from: [field], + to: divMod32Output, +})( + (x) => { + assert(x < 1n << 64n, `x needs to fit in 64bit, but got ${x}`); + return divMod32Helper(x); + }, + (x) => { + return Gadgets.divMod32(x); + } +); + +await equivalentAsync({ from: [field], to: divMod32Output }, { runs: 3 })( + (x) => { + assert(x < 1n << 64n, `x needs to fit in 64bit, but got ${x}`); + return divMod32Helper(x); + }, + async (x) => { + return (await Arithmetic.divMod32(x)).publicOutput; + } +); diff --git a/src/lib/provable/test/bitwise.unit-test.ts b/src/lib/provable/test/bitwise.unit-test.ts new file mode 100644 index 0000000000..1c3bbc8240 --- /dev/null +++ b/src/lib/provable/test/bitwise.unit-test.ts @@ -0,0 +1,277 @@ +import { ZkProgram } from '../../proof-system/zkprogram.js'; +import { + equivalentProvable as equivalent, + equivalentAsync, + field, + fieldWithRng, +} from '../../testing/equivalent.js'; +import { Fp, mod } from '../../../bindings/crypto/finite-field.js'; +import { Field } from '../wrapped.js'; +import { Gadgets } from '../gadgets/gadgets.js'; +import { Random } from '../../testing/property.js'; +import { + constraintSystem, + contains, + equals, + ifNotAllConstant, + repeat, + and, + withoutGenerics, +} from '../../testing/constraint-system.js'; +import { GateType } from '../../../snarky.js'; + +const maybeField = { + ...field, + rng: Random.map(Random.oneOf(Random.field, Random.field.invalid), (x) => + mod(x, Field.ORDER) + ), +}; + +let uint = (length: number) => fieldWithRng(Random.biguint(length)); + +let Bitwise = ZkProgram({ + name: 'bitwise', + publicOutput: Field, + methods: { + xor: { + privateInputs: [Field, Field], + async method(a: Field, b: Field) { + return Gadgets.xor(a, b, 254); + }, + }, + notUnchecked: { + privateInputs: [Field], + async method(a: Field) { + return Gadgets.not(a, 254, false); + }, + }, + notChecked: { + privateInputs: [Field], + async method(a: Field) { + return Gadgets.not(a, 254, true); + }, + }, + and: { + privateInputs: [Field, Field], + async method(a: Field, b: Field) { + return Gadgets.and(a, b, 64); + }, + }, + rot32: { + privateInputs: [Field], + async method(a: Field) { + return Gadgets.rotate32(a, 12, 'left'); + }, + }, + rot64: { + privateInputs: [Field], + async method(a: Field) { + return Gadgets.rotate64(a, 12, 'left'); + }, + }, + leftShift64: { + privateInputs: [Field], + async method(a: Field) { + return Gadgets.leftShift64(a, 12); + }, + }, + leftShift32: { + privateInputs: [Field], + async method(a: Field) { + Gadgets.rangeCheck32(a); + return Gadgets.leftShift32(a, 12); + }, + }, + rightShift64: { + privateInputs: [Field], + async method(a: Field) { + return Gadgets.rightShift64(a, 12); + }, + }, + }, +}); +await Bitwise.compile(); + +[2, 4, 8, 16, 32, 64, 128].forEach((length) => { + equivalent({ from: [uint(length), uint(length)], to: field })( + (x, y) => x ^ y, + (x, y) => Gadgets.xor(x, y, length) + ); + equivalent({ from: [uint(length), uint(length)], to: field })( + (x, y) => x & y, + (x, y) => Gadgets.and(x, y, length) + ); + // NOT unchecked + equivalent({ from: [uint(length)], to: field })( + (x) => Fp.not(x, length), + (x) => Gadgets.not(x, length, false) + ); + // NOT checked + equivalent({ from: [uint(length)], to: field })( + (x) => Fp.not(x, length), + (x) => Gadgets.not(x, length, true) + ); +}); + +[2, 4, 8, 16, 32, 64].forEach((length) => { + equivalent({ from: [uint(length)], to: field })( + (x) => Fp.rot(x, 12n, 'left'), + (x) => Gadgets.rotate64(x, 12, 'left') + ); + equivalent({ from: [uint(length)], to: field })( + (x) => Fp.leftShift(x, 12), + (x) => Gadgets.leftShift64(x, 12) + ); + equivalent({ from: [uint(length)], to: field })( + (x) => Fp.rightShift(x, 12), + (x) => Gadgets.rightShift64(x, 12) + ); +}); + +[2, 4, 8, 16, 32].forEach((length) => { + equivalent({ from: [uint(length)], to: field })( + (x) => Fp.rot(x, 12n, 'left', 32n), + (x) => Gadgets.rotate32(x, 12, 'left') + ); + equivalent({ from: [uint(length)], to: field })( + (x) => Fp.leftShift(x, 12, 32), + (x) => Gadgets.leftShift32(x, 12) + ); +}); + +const runs = 2; + +await equivalentAsync({ from: [uint(64), uint(64)], to: field }, { runs })( + (x, y) => { + return x ^ y; + }, + async (x, y) => { + let proof = await Bitwise.xor(x, y); + return proof.publicOutput; + } +); + +await equivalentAsync({ from: [maybeField], to: field }, { runs })( + (x) => { + return Fp.not(x, 254); + }, + async (x) => { + let proof = await Bitwise.notUnchecked(x); + return proof.publicOutput; + } +); +await equivalentAsync({ from: [maybeField], to: field }, { runs })( + (x) => { + if (x > 2n ** 254n) throw Error('Does not fit into 254 bit'); + return Fp.not(x, 254); + }, + async (x) => { + let proof = await Bitwise.notChecked(x); + return proof.publicOutput; + } +); + +await equivalentAsync({ from: [maybeField, maybeField], to: field }, { runs })( + (x, y) => { + if (x >= 2n ** 64n || y >= 2n ** 64n) + throw Error('Does not fit into 64 bits'); + return x & y; + }, + async (x, y) => { + let proof = await Bitwise.and(x, y); + return proof.publicOutput; + } +); + +await equivalentAsync({ from: [field], to: field }, { runs })( + (x) => { + if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); + return Fp.rot(x, 12n, 'left'); + }, + async (x) => { + let proof = await Bitwise.rot64(x); + return proof.publicOutput; + } +); + +await equivalentAsync({ from: [uint(32)], to: uint(32) }, { runs })( + (x) => { + return Fp.rot(x, 12n, 'left', 32n); + }, + async (x) => { + let proof = await Bitwise.rot32(x); + return proof.publicOutput; + } +); + +await equivalentAsync({ from: [field], to: field }, { runs })( + (x) => { + if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); + return Fp.leftShift(x, 12); + }, + async (x) => { + let proof = await Bitwise.leftShift64(x); + return proof.publicOutput; + } +); + +await equivalentAsync({ from: [field], to: field }, { runs })( + (x) => { + if (x >= 1n << 32n) throw Error('Does not fit into 32 bits'); + return Fp.leftShift(x, 12, 32); + }, + async (x) => { + let proof = await Bitwise.leftShift32(x); + return proof.publicOutput; + } +); + +await equivalentAsync({ from: [field], to: field }, { runs })( + (x) => { + if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); + return Fp.rightShift(x, 12); + }, + async (x) => { + let proof = await Bitwise.rightShift64(x); + return proof.publicOutput; + } +); + +// check that gate chains stay intact + +function xorChain(bits: number) { + return repeat(Math.ceil(bits / 16), 'Xor16').concat('Zero'); +} + +constraintSystem.fromZkProgram( + Bitwise, + 'xor', + ifNotAllConstant(contains(xorChain(254))) +); + +constraintSystem.fromZkProgram( + Bitwise, + 'notChecked', + ifNotAllConstant(contains(xorChain(254))) +); + +constraintSystem.fromZkProgram( + Bitwise, + 'notUnchecked', + ifNotAllConstant(contains('Generic')) +); + +constraintSystem.fromZkProgram( + Bitwise, + 'and', + ifNotAllConstant(contains(xorChain(64))) +); + +let rotChain: GateType[] = ['Rot64', 'RangeCheck0']; +let isJustRotate = ifNotAllConstant( + and(contains(rotChain), withoutGenerics(equals(rotChain))) +); + +constraintSystem.fromZkProgram(Bitwise, 'rot64', isJustRotate); +constraintSystem.fromZkProgram(Bitwise, 'leftShift64', isJustRotate); +constraintSystem.fromZkProgram(Bitwise, 'rightShift64', isJustRotate); diff --git a/src/lib/provable/test/ecdsa.unit-test.ts b/src/lib/provable/test/ecdsa.unit-test.ts new file mode 100644 index 0000000000..97e9b93f30 --- /dev/null +++ b/src/lib/provable/test/ecdsa.unit-test.ts @@ -0,0 +1,192 @@ +import { createCurveAffine } from '../../../bindings/crypto/elliptic-curve.js'; +import { + Ecdsa, + EllipticCurve, + Point, + initialAggregator, + verifyEcdsaConstant, +} from '../gadgets/elliptic-curve.js'; +import { Field3 } from '../gadgets/foreign-field.js'; +import { CurveParams } from '../../../bindings/crypto/elliptic-curve-examples.js'; +import { Provable } from '../provable.js'; +import { ZkProgram } from '../../proof-system/zkprogram.js'; +import { assert } from '../gadgets/common.js'; +import { foreignField, uniformForeignField } from './test-utils.js'; +import { + First, + Second, + bool, + equivalentProvable, + fromRandom, + map, + oneOf, + record, +} from '../../testing/equivalent.js'; +import { Bool } from '../bool.js'; +import { Random } from '../../testing/random.js'; + +// quick tests +const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); +const Pallas = createCurveAffine(CurveParams.Pallas); +const Vesta = createCurveAffine(CurveParams.Vesta); +let curves = [Secp256k1, Pallas, Vesta]; + +for (let Curve of curves) { + // prepare test inputs + let field = foreignField(Curve.Field); + let scalar = foreignField(Curve.Scalar); + let privateKey = uniformForeignField(Curve.Scalar); + + // correct signature shape, but independently random components, which will never form a valid signature + let badSignature = record({ + signature: record({ r: scalar, s: scalar }), + msg: scalar, + publicKey: record({ x: field, y: field }), + }); + + let signatureInputs = record({ privateKey, msg: scalar }); + + let signature = map( + { from: signatureInputs, to: badSignature }, + ({ privateKey, msg }) => { + let publicKey = Curve.scale(Curve.one, privateKey); + let signature = Ecdsa.sign(Curve, msg, privateKey); + return { signature, msg, publicKey }; + } + ); + + // with 30% prob, test the version without GLV even if the curve supports it + let noGlv = fromRandom(Random.map(Random.fraction(), (f) => f < 0.3)); + + // provable method we want to test + const verify = (sig: Second, noGlv: boolean) => { + // invalid public key can lead to either a failing constraint, or verify() returning false + EllipticCurve.assertOnCurve(sig.publicKey, Curve); + + // additional checks which are inconsistent between constant and variable verification + let { s, r } = sig.signature; + if (Field3.isConstant(s)) { + assert(Field3.toBigint(s) !== 0n, 'invalid signature (s=0)'); + } + if (Field3.isConstant(r)) { + assert(Field3.toBigint(r) !== 0n, 'invalid signature (r=0)'); + } + + let hasGlv = Curve.hasEndomorphism; + if (noGlv) Curve.hasEndomorphism = false; // hack to force non-GLV version + try { + return Ecdsa.verify(Curve, sig.signature, sig.msg, sig.publicKey); + } finally { + Curve.hasEndomorphism = hasGlv; + } + }; + + // input validation equivalent to the one implicit in verify() + const checkInputs = ({ + signature: { r, s }, + publicKey, + }: First) => { + assert(r !== 0n && s !== 0n, 'invalid signature'); + let pk = Curve.fromNonzero(publicKey); + assert(Curve.isOnCurve(pk), 'invalid public key'); + return true; + }; + + // positive test + equivalentProvable({ from: [signature, noGlv], to: bool, verbose: true })( + () => true, + verify, + `${Curve.name}: verifies` + ); + + // negative test + equivalentProvable({ from: [badSignature, noGlv], to: bool, verbose: true })( + (s) => checkInputs(s) && false, + verify, + `${Curve.name}: fails` + ); + + // test against constant implementation, with both invalid and valid signatures + equivalentProvable({ + from: [oneOf(signature, badSignature), noGlv], + to: bool, + verbose: true, + })( + ({ signature, publicKey, msg }) => { + checkInputs({ signature, publicKey, msg }); + return verifyEcdsaConstant(Curve, signature, msg, publicKey); + }, + verify, + `${Curve.name}: verify` + ); +} + +// full end-to-end test with proving + +let publicKey = Point.from({ + x: 49781623198970027997721070672560275063607048368575198229673025608762959476014n, + y: 44999051047832679156664607491606359183507784636787036192076848057884504239143n, +}); + +let signature = Ecdsa.Signature.fromHex( + '0x82de9950cc5aac0dca7210cb4b77320ac9e844717d39b1781e9d941d920a12061da497b3c134f50b2fce514d66e20c5e43f9615f097395a5527041d14860a52f1b' +); + +let msgHash = + Field3.from( + 0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn + ); + +const ia = initialAggregator(Secp256k1); +const config = { G: { windowSize: 4 }, P: { windowSize: 4 }, ia }; + +let program = ZkProgram({ + name: 'ecdsa', + publicOutput: Bool, + methods: { + ecdsa: { + privateInputs: [], + async method() { + let signature_ = Provable.witness( + Ecdsa.Signature.provable, + () => signature + ); + let msgHash_ = Provable.witness(Field3.provable, () => msgHash); + let publicKey_ = Provable.witness(Point.provable, () => publicKey); + + return Ecdsa.verify( + Secp256k1, + signature_, + msgHash_, + publicKey_, + config + ); + }, + }, + }, +}); + +console.time('ecdsa verify (constant)'); +program.rawMethods.ecdsa(); +console.timeEnd('ecdsa verify (constant)'); + +console.time('ecdsa verify (witness gen / check)'); +await Provable.runAndCheck(program.rawMethods.ecdsa); +console.timeEnd('ecdsa verify (witness gen / check)'); + +console.time('ecdsa verify (build constraint system)'); +let cs = (await program.analyzeMethods()).ecdsa; +console.timeEnd('ecdsa verify (build constraint system)'); + +console.log(cs.summary()); + +console.time('ecdsa verify (compile)'); +await program.compile(); +console.timeEnd('ecdsa verify (compile)'); + +console.time('ecdsa verify (prove)'); +let proof = await program.ecdsa(); +console.timeEnd('ecdsa verify (prove)'); + +assert(await program.verify(proof), 'proof verifies'); +proof.publicOutput.assertTrue('signature verifies'); diff --git a/src/lib/provable/test/elliptic-curve.unit-test.ts b/src/lib/provable/test/elliptic-curve.unit-test.ts new file mode 100644 index 0000000000..2db23ecd9d --- /dev/null +++ b/src/lib/provable/test/elliptic-curve.unit-test.ts @@ -0,0 +1,86 @@ +import { CurveParams } from '../../../bindings/crypto/elliptic-curve-examples.js'; +import { createCurveAffine } from '../../../bindings/crypto/elliptic-curve.js'; +import { + array, + equivalentProvable, + map, + onlyIf, + spec, + unit, +} from '../../testing/equivalent.js'; +import { Random } from '../../testing/random.js'; +import { assert } from '../gadgets/common.js'; +import { + EllipticCurve, + Point, + simpleMapToCurve, +} from '../gadgets/elliptic-curve.js'; +import { foreignField, throwError } from './test-utils.js'; + +// provable equivalence tests +const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); +const Pallas = createCurveAffine(CurveParams.Pallas); +const Vesta = createCurveAffine(CurveParams.Vesta); +let curves = [Secp256k1, Pallas, Vesta]; + +for (let Curve of curves) { + // prepare test inputs + let field = foreignField(Curve.Field); + let scalar = foreignField(Curve.Scalar); + + // point shape, but with independently random components, which will never form a valid point + let badPoint = spec({ + rng: Random.record({ + x: field.rng, + y: field.rng, + infinity: Random.constant(false), + }), + there: Point.from, + back: Point.toBigint, + provable: Point.provable, + }); + + // valid random point + let point = map({ from: field, to: badPoint }, (x) => + simpleMapToCurve(x, Curve) + ); + + // two random points that are not equal, so are a valid input to EC addition + let unequalPair = onlyIf(array(point, 2), ([p, q]) => !Curve.equal(p, q)); + + // test ec gadgets witness generation + + equivalentProvable({ from: [unequalPair], to: point, verbose: true })( + ([p, q]) => Curve.add(p, q), + ([p, q]) => EllipticCurve.add(p, q, Curve), + `${Curve.name} add` + ); + + equivalentProvable({ from: [point], to: point, verbose: true })( + Curve.double, + (p) => EllipticCurve.double(p, Curve), + `${Curve.name} double` + ); + + equivalentProvable({ from: [point], to: point, verbose: true })( + Curve.negate, + (p) => EllipticCurve.negate(p, Curve), + `${Curve.name} negate` + ); + + equivalentProvable({ from: [point], to: unit, verbose: true })( + (p) => Curve.isOnCurve(p) || throwError('expect on curve'), + (p) => EllipticCurve.assertOnCurve(p, Curve), + `${Curve.name} on curve` + ); + + equivalentProvable({ from: [point, scalar], to: point, verbose: true })( + (p, s) => { + let sp = Curve.scale(p, s); + assert(!sp.infinity, 'expect nonzero'); + return sp; + }, + (p, s) => EllipticCurve.scale(s, p, Curve), + `${Curve.name} scale` + ); +} diff --git a/src/lib/provable/test/field.unit-test.ts b/src/lib/provable/test/field.unit-test.ts new file mode 100644 index 0000000000..d0a4efebde --- /dev/null +++ b/src/lib/provable/test/field.unit-test.ts @@ -0,0 +1,210 @@ +import { Field } from '../wrapped.js'; +import { Fp } from '../../../bindings/crypto/finite-field.js'; +import { BinableFp } from '../../../mina-signer/src/field-bigint.js'; +import { test, Random } from '../../testing/property.js'; +import { deepEqual, throws } from 'node:assert/strict'; +import { Provable } from '../provable.js'; +import { Binable } from '../../../bindings/lib/binable.js'; +import { ProvableExtended } from '../types/struct.js'; +import { FieldType } from '../core/fieldvar.js'; +import { + equivalentProvable as equivalent, + oneOf, + field, + bigintField, + throwError, + unit, + bool, +} from '../../testing/equivalent.js'; +import { runAndCheckSync } from '../core/provable-context.js'; +import { ProvablePure } from '../types/provable-intf.js'; +import { assert } from '../../util/assert.js'; + +// types +Field satisfies Provable; +Field satisfies ProvablePure; +Field satisfies ProvableExtended; +Field satisfies Binable; + +// constructor +test(Random.field, Random.json.field, (x, y, assert) => { + let z = Field(x); + assert(z instanceof Field); + assert(z.toBigInt() === x); + assert(z.toString() === x.toString()); + assert(z.isConstant()); + deepEqual(z.toConstant(), z); + + assert((z = new Field(x)) instanceof Field && z.toBigInt() === x); + assert((z = Field(z)) instanceof Field && z.toBigInt() === x); + assert((z = Field(z.value)) instanceof Field && z.toBigInt() === x); + + z = Field(y); + assert(z instanceof Field); + assert(z.toString() === y); + deepEqual(Field.fromJSON(y), z); + assert(z.toJSON() === y); +}); + +// handles small numbers +test(Random.nat(1000), (n, assert) => { + assert(Field(n).toString() === String(n)); +}); +// handles large numbers 2^31 <= x < 2^53 +test(Random.int(2 ** 31, Number.MAX_SAFE_INTEGER), (n, assert) => { + assert(Field(n).toString() === String(n)); +}); +// handles negative numbers +test(Random.uint32, (n) => { + deepEqual(Field(-n), Field(n).neg()); +}); +// throws on fractional numbers +test.negative(Random.int(-10, 10), Random.fraction(1), (x, f) => { + Field(x + f); +}); +// correctly overflows the field +test(Random.field, Random.int(-5, 5), (x, k) => { + deepEqual(Field(x + BigInt(k) * Field.ORDER), Field(x)); +}); + +// Field | bigint parameter +let fieldOrBigint = oneOf(field, bigintField); + +// arithmetic, both in- and outside provable code +let equivalent1 = equivalent({ from: [field], to: field }); +let equivalent2 = equivalent({ from: [field, fieldOrBigint], to: field }); + +equivalent2(Fp.add, (x, y) => x.add(y)); +equivalent1(Fp.negate, (x) => x.neg()); +equivalent2(Fp.sub, (x, y) => x.sub(y)); +equivalent2(Fp.mul, (x, y) => x.mul(y)); +equivalent1( + (x) => Fp.inverse(x) ?? throwError('division by 0'), + (x) => x.inv() +); +equivalent2( + (x, y) => Fp.div(x, y) ?? throwError('division by 0'), + (x, y) => x.div(y) +); +equivalent1(Fp.square, (x) => x.square()); +equivalent1( + (x) => Fp.sqrt(x) ?? throwError('no sqrt'), + (x) => x.sqrt() +); +equivalent({ from: [field, fieldOrBigint], to: bool })( + (x, y) => x === y, + (x, y) => x.equals(y) +); + +equivalent({ from: [field, fieldOrBigint], to: bool })( + (x, y) => x < y, + (x, y) => x.lessThan(y) +); +equivalent({ from: [field, fieldOrBigint], to: bool })( + (x, y) => x <= y, + (x, y) => x.lessThanOrEqual(y) +); +equivalent({ from: [field, fieldOrBigint], to: unit })( + (x, y) => x === y || throwError('not equal'), + (x, y) => x.assertEquals(y) +); +equivalent({ from: [field, fieldOrBigint], to: unit })( + (x, y) => x !== y || throwError('equal'), + (x, y) => x.assertNotEquals(y) +); +equivalent({ from: [field, fieldOrBigint], to: unit })( + (x, y) => x < y || throwError('not less than'), + (x, y) => x.assertLessThan(y) +); +equivalent({ from: [field, fieldOrBigint], to: unit })( + (x, y) => x <= y || throwError('not less than or equal'), + (x, y) => x.assertLessThanOrEqual(y) +); +equivalent({ from: [field], to: bool })( + (x) => { + assert(x === 0n || x === 1n, 'not boolean'); + return x === 1n; + }, + (x) => x.assertBool() +); +equivalent({ from: [field], to: unit })( + (x) => x === 0n || x === 1n || throwError('not boolean'), + (x) => { + let y = Provable.witness(Field, () => x.div(2)); + y.mul(2).assertBool(); + } +); +equivalent({ from: [field], to: bool })( + (x) => (x & 1n) === 0n, + (x) => x.isEven() +); + +// non-constant field vars +test(Random.field, (x0, assert) => { + runAndCheckSync(() => { + // Var + let x = Provable.witness(Field, () => Field(x0)); + assert(x.value[0] === FieldType.Var); + assert(typeof x.value[1] === 'number'); + throws(() => x.toConstant()); + throws(() => x.toBigInt()); + Provable.asProver(() => assert(x.toBigInt() === x0)); + + // Scale + let z = x.mul(2); + assert(z.value[0] === FieldType.Scale); + throws(() => x.toConstant()); + + // Add + let u = z.add(x); + assert(u.value[0] === FieldType.Add); + throws(() => x.toConstant()); + Provable.asProver(() => assert(u.toBigInt() === Fp.mul(x0, 3n))); + + // seal + let v = u.seal(); + assert(v.value[0] === FieldType.Var); + Provable.asProver(() => assert(v.toBigInt() === Fp.mul(x0, 3n))); + + // Provable.witness / assertEquals / assertNotEquals + let w0 = Provable.witness(Field, () => v.mul(5).add(1)); + let w1 = x.mul(15).add(1); + w0.assertEquals(w1); + throws(() => w0.assertNotEquals(w1)); + + let w2 = Provable.witness(Field, () => w0.add(1)); + w0.assertNotEquals(w2); + throws(() => w0.assertEquals(w2)); + }); +}); + +// some provable operations +test(Random.field, Random.field, (x0, y0, assert) => { + runAndCheckSync(() => { + // equals + let x = Provable.witness(Field, () => Field(x0)); + let y = Provable.witness(Field, () => Field(y0)); + + let b = x.equals(y); + b.assertEquals(x0 === y0); + Provable.asProver(() => assert(b.toBoolean() === (x0 === y0))); + + let c = x.equals(x0); + c.assertEquals(true); + Provable.asProver(() => assert(c.toBoolean())); + + // mul + let z = x.mul(y); + Provable.asProver(() => assert(z.toBigInt() === Fp.mul(x0, y0))); + + // toBits / fromBits + // BinableFp.toBits() returns 255 bits, but our new to/from impl only accepts <=254 + // https://github.com/o1-labs/o1js/pull/1461 + let bits = BinableFp.toBits(x0).slice(0, -1); + let x1 = Provable.witness(Field, () => Field.fromBits(bits)); + let bitsVars = x1.toBits(); + Provable.asProver(() => + assert(bitsVars.every((b, i) => b.toBoolean() === bits[i])) + ); + }); +}); diff --git a/src/lib/provable/test/foreign-curve.unit-test.ts b/src/lib/provable/test/foreign-curve.unit-test.ts new file mode 100644 index 0000000000..2861002421 --- /dev/null +++ b/src/lib/provable/test/foreign-curve.unit-test.ts @@ -0,0 +1,42 @@ +import { createForeignCurve } from '../crypto/foreign-curve.js'; +import { Fq } from '../../../bindings/crypto/finite-field.js'; +import { Vesta as V } from '../../../bindings/crypto/elliptic-curve.js'; +import { Provable } from '../provable.js'; +import { Field } from '../field.js'; +import { Crypto } from '../crypto/crypto.js'; + +class Vesta extends createForeignCurve(Crypto.CurveParams.Vesta) {} +class Fp extends Vesta.Scalar {} + +let g = { x: Fq.negate(1n), y: 2n, infinity: false }; +let h = V.toAffine(V.negate(V.double(V.add(V.fromAffine(g), V.one)))); +let scalar = Field.random().toBigInt(); +let p = V.toAffine(V.scale(V.fromAffine(h), scalar)); + +function main() { + let g0 = Provable.witness(Vesta.provable, () => new Vesta(g)); + let one = Provable.witness(Vesta.provable, () => Vesta.generator); + let h0 = g0.add(one).double().negate(); + Provable.assertEqual(Vesta.provable, h0, new Vesta(h)); + + h0.assertOnCurve(); + h0.assertInSubgroup(); + + let scalar0 = Provable.witness(Fp.provable, () => new Fp(scalar)); + let p0 = h0.scale(scalar0); + Provable.assertEqual(Vesta.provable, p0, new Vesta(p)); +} + +console.time('running constant version'); +main(); +console.timeEnd('running constant version'); + +console.time('running witness generation & checks'); +await Provable.runAndCheck(main); +console.timeEnd('running witness generation & checks'); + +console.time('creating constraint system'); +let cs = await Provable.constraintSystem(main); +console.timeEnd('creating constraint system'); + +console.log(cs.summary()); diff --git a/src/lib/provable/test/foreign-field-gadgets.unit-test.ts b/src/lib/provable/test/foreign-field-gadgets.unit-test.ts new file mode 100644 index 0000000000..c4f4c517d1 --- /dev/null +++ b/src/lib/provable/test/foreign-field-gadgets.unit-test.ts @@ -0,0 +1,383 @@ +import type { FiniteField } from '../../../bindings/crypto/finite-field.js'; +import { exampleFields } from '../../../bindings/crypto/finite-field-examples.js'; +import { + array, + equivalent, + equivalentAsync, + equivalentProvable, + fromRandom, + record, + unit, +} from '../../testing/equivalent.js'; +import { Random } from '../../testing/random.js'; +import { Field3, Gadgets } from '../gadgets/gadgets.js'; +import { ZkProgram } from '../../proof-system/zkprogram.js'; +import { Provable } from '../provable.js'; +import { assert } from '../gadgets/common.js'; +import { + allConstant, + and, + constraintSystem, + contains, + equals, + ifNotAllConstant, + not, + or, + repeat, + withoutGenerics, +} from '../../testing/constraint-system.js'; +import { GateType } from '../../../snarky.js'; +import { AnyTuple } from '../../util/types.js'; +import { + foreignField, + throwError, + unreducedForeignField, +} from './test-utils.js'; +import { l2 } from '../gadgets/range-check.js'; + +const { ForeignField } = Gadgets; + +let sign = fromRandom(Random.oneOf(1n as const, -1n as const)); + +let fields = [ + exampleFields.small, + exampleFields.babybear, + exampleFields.f25519, + exampleFields.secp256k1, + exampleFields.secq256k1, + exampleFields.bls12_381_scalar, + exampleFields.Fq, + exampleFields.Fp, +]; + +// tests for witness generation + +for (let F of fields) { + let f = foreignField(F); + let eq2 = equivalentProvable({ from: [f, f], to: f }); + + eq2(F.add, (x, y) => ForeignField.add(x, y, F.modulus), 'add'); + eq2(F.sub, (x, y) => ForeignField.sub(x, y, F.modulus), 'sub'); + eq2(F.mul, (x, y) => ForeignField.mul(x, y, F.modulus), 'mul'); + equivalentProvable({ from: [f], to: f })( + (x) => F.inverse(x) ?? throwError('no inverse'), + (x) => ForeignField.inv(x, F.modulus), + 'inv' + ); + eq2( + (x, y) => F.div(x, y) ?? throwError('no inverse'), + (x, y) => ForeignField.div(x, y, F.modulus), + 'div' + ); + equivalentProvable({ from: [f, f], to: unit })( + (x, y) => assertMulExampleNaive(Field3.from(x), Field3.from(y), F.modulus), + (x, y) => assertMulExample(x, y, F.modulus), + 'assertMul' + ); + // test for assertMul which mostly tests the negative case because for random inputs, we expect + // (x - y) * z != a + b + equivalentProvable({ from: [f, f, f, f, f], to: unit })( + (x, y, z, a, b) => assert(F.mul(F.sub(x, y), z) === F.add(a, b)), + (x, y, z, a, b) => + ForeignField.assertMul( + ForeignField.Sum(x).sub(y), + z, + ForeignField.Sum(a).add(b), + F.modulus + ), + 'assertMul negative' + ); + + // tests with inputs that aren't reduced mod f + let big264 = unreducedForeignField(264, F); // this is the max size supported by our range checks / ffadd + let big258 = unreducedForeignField(258, F); // rough max size supported by ffmul + + // addition can fail on two unreduced inputs because we can get x + y - f > 2^264 + equivalentProvable({ from: [big264, f], to: big264 })( + F.add, + (x, y) => ForeignField.add(x, y, F.modulus), + 'add unreduced' + ); + // subtraction doesn't work with unreduced y because the range check on the result prevents x-y < -f + equivalentProvable({ from: [big264, f], to: big264 })( + F.sub, + (x, y) => ForeignField.sub(x, y, F.modulus), + 'sub unreduced' + ); + equivalentProvable({ from: [big258, big258], to: f })( + F.mul, + (x, y) => ForeignField.mul(x, y, F.modulus), + 'mul unreduced' + ); + equivalentProvable({ from: [big258], to: f })( + (x) => F.inverse(x) ?? throwError('no inverse'), + (x) => ForeignField.inv(x, F.modulus), + 'inv unreduced' + ); + // the div() gadget doesn't work with unreduced x because the backwards check (x/y)*y === x fails + // and it's not valid with unreduced y because we only assert y != 0, y != f but it can be 2f, 3f, etc. + // the combination of inv() and mul() is more flexible (but much more expensive, ~40 vs ~30 constraints) + equivalentProvable({ from: [big258, big258], to: f })( + (x, y) => F.div(x, y) ?? throwError('no inverse'), + (x, y) => ForeignField.mul(x, ForeignField.inv(y, F.modulus), F.modulus), + 'div unreduced' + ); + + equivalent({ from: [big264], to: unit })( + (x) => assertWeakBound(x, F.modulus), + (x) => ForeignField.assertAlmostReduced([x], F.modulus) + ); + + equivalentProvable({ from: [big264, big264], to: unit })( + (x, y) => assert(x < y, 'not less than'), + (x, y) => ForeignField.assertLessThan(x, y) + ); + + equivalentProvable({ from: [big264, big264], to: unit })( + (x, y) => assert(x <= y, 'not less than or equal'), + (x, y) => ForeignField.assertLessThanOrEqual(x, y) + ); + + // sumchain of 5 + equivalentProvable({ from: [array(f, 5), array(sign, 4)], to: f })( + (xs, signs) => sum(xs, signs, F), + (xs, signs) => ForeignField.sum(xs, signs, F.modulus), + 'sumchain 5' + ); + + // sumchain up to 100 + let operands = array(record({ x: f, sign }), Random.nat(100)); + + equivalentProvable({ from: [f, operands], to: f })( + (x0, ts) => { + let xs = [x0, ...ts.map((t) => t.x)]; + let signs = ts.map((t) => t.sign); + return sum(xs, signs, F); + }, + (x0, ts) => { + let xs = [x0, ...ts.map((t) => t.x)]; + let signs = ts.map((t) => t.sign); + return ForeignField.sum(xs, signs, F.modulus); + }, + 'sumchain long' + ); +} + +// setup zk program tests + +let F = exampleFields.secp256k1; +let f = foreignField(F); +let big264 = unreducedForeignField(264, F); +let chainLength = 5; +let signs = [1n, -1n, -1n, 1n] satisfies (-1n | 1n)[]; + +let ffProgram = ZkProgram({ + name: 'foreign-field', + publicOutput: Field3.provable, + methods: { + sumchain: { + privateInputs: [Provable.Array(Field3.provable, chainLength)], + async method(xs) { + return ForeignField.sum(xs, signs, F.modulus); + }, + }, + mulWithBoundsCheck: { + privateInputs: [Field3.provable, Field3.provable], + async method(x, y) { + ForeignField.assertAlmostReduced([x, y], F.modulus); + return ForeignField.mul(x, y, F.modulus); + }, + }, + mul: { + privateInputs: [Field3.provable, Field3.provable], + async method(x, y) { + return ForeignField.mul(x, y, F.modulus); + }, + }, + inv: { + privateInputs: [Field3.provable], + async method(x) { + return ForeignField.inv(x, F.modulus); + }, + }, + div: { + privateInputs: [Field3.provable, Field3.provable], + async method(x, y) { + return ForeignField.div(x, y, F.modulus); + }, + }, + assertLessThan: { + privateInputs: [Field3.provable, Field3.provable], + async method(x, y) { + ForeignField.assertLessThan(x, y); + return x; + }, + }, + }, +}); + +// tests for constraint system + +function addChain(length: number) { + return repeat(length - 1, 'ForeignFieldAdd').concat('Zero'); +} +let mrc: GateType[] = ['RangeCheck0', 'RangeCheck0', 'RangeCheck1', 'Zero']; + +constraintSystem.fromZkProgram( + ffProgram, + 'sumchain', + ifNotAllConstant( + and( + contains([addChain(chainLength), mrc]), + withoutGenerics(equals([...addChain(chainLength), ...mrc])) + ) + ) +); + +let mulChain: GateType[] = ['ForeignFieldMul', 'Zero']; +let mulLayout = ifNotAllConstant( + and( + contains([mulChain, mrc, mrc, mrc]), + withoutGenerics(equals([...mulChain, ...repeat(3, mrc)])) + ) +); +let invLayout = ifNotAllConstant( + and( + contains([mrc, mulChain, mrc, mrc, mrc]), + withoutGenerics(equals([...mrc, ...mulChain, ...repeat(3, mrc)])) + ) +); + +constraintSystem.fromZkProgram(ffProgram, 'mul', mulLayout); +constraintSystem.fromZkProgram(ffProgram, 'inv', invLayout); +constraintSystem.fromZkProgram(ffProgram, 'div', invLayout); + +// tests with proving + +const runs = 2; + +await ffProgram.compile(); + +await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs })( + (xs) => sum(xs, signs, F), + async (xs) => { + let proof = await ffProgram.sumchain(xs); + assert(await ffProgram.verify(proof), 'verifies'); + return proof.publicOutput; + }, + 'prove chain' +); + +await equivalentAsync({ from: [big264, big264], to: f }, { runs })( + (x, y) => { + assertWeakBound(x, F.modulus); + assertWeakBound(y, F.modulus); + return F.mul(x, y); + }, + async (x, y) => { + let proof = await ffProgram.mulWithBoundsCheck(x, y); + assert(await ffProgram.verify(proof), 'verifies'); + return proof.publicOutput; + }, + 'prove mul' +); + +await equivalentAsync({ from: [f, f], to: f }, { runs })( + (x, y) => F.div(x, y) ?? throwError('no inverse'), + async (x, y) => { + let proof = await ffProgram.div(x, y); + assert(await ffProgram.verify(proof), 'verifies'); + return proof.publicOutput; + }, + 'prove div' +); + +await equivalentAsync({ from: [f, f], to: unit }, { runs })( + (x, y) => assert(x < y, 'not less than'), + async (x, y) => { + let proof = await ffProgram.assertLessThan(x, y); + assert(await ffProgram.verify(proof), 'verifies'); + }, + 'prove less than' +); + +// assert mul example +// (x - y) * (x + y) = x^2 - y^2 + +function assertMulExample(x: Field3, y: Field3, f: bigint) { + // witness x^2, y^2 + let x2 = Provable.witness(Field3.provable, () => ForeignField.mul(x, x, f)); + let y2 = Provable.witness(Field3.provable, () => ForeignField.mul(y, y, f)); + + // assert (x - y) * (x + y) = x^2 - y^2 + let xMinusY = ForeignField.Sum(x).sub(y); + let xPlusY = ForeignField.Sum(x).add(y); + let x2MinusY2 = ForeignField.Sum(x2).sub(y2); + ForeignField.assertMul(xMinusY, xPlusY, x2MinusY2, f); +} + +function assertMulExampleNaive(x: Field3, y: Field3, f: bigint) { + // witness x^2, y^2 + let x2 = Provable.witness(Field3.provable, () => ForeignField.mul(x, x, f)); + let y2 = Provable.witness(Field3.provable, () => ForeignField.mul(y, y, f)); + + // assert (x - y) * (x + y) = x^2 - y^2 + let lhs = ForeignField.mul( + ForeignField.sub(x, y, f), + ForeignField.add(x, y, f), + f + ); + let rhs = ForeignField.sub(x2, y2, f); + Provable.assertEqual(Field3.provable, lhs, rhs); +} + +let from2 = { from: [f, f] satisfies AnyTuple }; +let gates = constraintSystem.size(from2, (x, y) => + assertMulExample(x, y, F.modulus) +); +let gatesNaive = constraintSystem.size(from2, (x, y) => + assertMulExampleNaive(x, y, F.modulus) +); +// the assertMul() version should save 11.5 rows: +// -2*1.5 rows by replacing input MRCs with low-limb ffadd +// -2*4 rows for avoiding the MRC on both mul() and sub() outputs +// -1 row for chaining one ffadd into ffmul +// +0.5 rows for having to combine the two lower result limbs before wiring to ffmul remainder +assert(gates + 11 <= gatesNaive, 'assertMul() saves at least 11 constraints'); + +let addChainedIntoMul: GateType[] = ['ForeignFieldAdd', ...mulChain]; + +constraintSystem( + 'assert mul', + from2, + (x, y) => assertMulExample(x, y, F.modulus), + or( + and( + contains([addChain(2), addChain(2), addChainedIntoMul]), + // assertMul() doesn't use any range checks besides on internal values and the quotient + containsNTimes(2, mrc) + ), + allConstant + ) +); + +// helper + +function containsNTimes(n: number, pattern: readonly GateType[]) { + return and( + contains(repeat(n, pattern)), + not(contains(repeat(n + 1, pattern))) + ); +} + +function sum(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { + let sum = xs[0]; + for (let i = 0; i < signs.length; i++) { + sum = signs[i] === 1n ? F.add(sum, xs[i + 1]) : F.sub(sum, xs[i + 1]); + } + return sum; +} + +function assertWeakBound(x: bigint, f: bigint) { + assert(x >= 0n && x >> l2 <= f >> l2, 'weak bound'); +} diff --git a/src/lib/provable/test/foreign-field.unit-test.ts b/src/lib/provable/test/foreign-field.unit-test.ts new file mode 100644 index 0000000000..c7f228ddaf --- /dev/null +++ b/src/lib/provable/test/foreign-field.unit-test.ts @@ -0,0 +1,190 @@ +import { Field, Group } from '../wrapped.js'; +import { ForeignField, createForeignField } from '../foreign-field.js'; +import { Fq } from '../../../bindings/crypto/finite-field.js'; +import { Pallas } from '../../../bindings/crypto/elliptic-curve.js'; +import { expect } from 'expect'; +import { + bool, + equivalentProvable as equivalent, + equivalent as equivalentNonProvable, + first, + spec, + throwError, + unit, +} from '../../testing/equivalent.js'; +import { test, Random } from '../../testing/property.js'; +import { Provable } from '../provable.js'; +import { Circuit, circuitMain } from '../../proof-system/circuit.js'; +import { Scalar } from '../scalar.js'; +import { l } from '../gadgets/range-check.js'; +import { assert } from '../gadgets/common.js'; +import { ProvablePure } from '../types/provable-intf.js'; + +// toy example - F_17 + +class SmallField extends createForeignField(17n) {} + +let x = SmallField.from(16); +x.assertEquals(-1); // 16 = -1 (mod 17) +x.mul(x).assertEquals(1); // 16 * 16 = 15 * 17 + 1 = 1 (mod 17) + +// invalid example - modulus too large + +expect(() => createForeignField(1n << 260n)).toThrow( + 'modulus exceeds the max supported size' +); + +// real example - foreign field arithmetic in the Pallas scalar field + +class ForeignScalar extends createForeignField(Fq.modulus) {} + +// types +ForeignScalar.provable satisfies ProvablePure; + +// basic constructor / IO +{ + let s0 = 1n + ((1n + (1n << l)) << l); + let scalar = new ForeignScalar(s0); + + expect(scalar.value).toEqual([Field(1), Field(1), Field(1)]); + expect(scalar.toBigInt()).toEqual(s0); +} + +test(Random.scalar, (x0, assert) => { + let x = new ForeignScalar(x0); + assert(x.toBigInt() === x0); + assert(x.isConstant()); +}); + +// test equivalence of in-SNARK and out-of-SNARK operations + +let f = spec({ + rng: Random.scalar, + there: ForeignScalar.from, + back: (x) => x.toBigInt(), + provable: ForeignScalar.AlmostReduced.provable, +}); +let u264 = spec({ + rng: Random.bignat(1n << 264n), + there: ForeignScalar.from, + back: (x) => x.toBigInt(), + provable: ForeignScalar.Unreduced.provable, +}); + +// arithmetic +equivalent({ from: [f, f], to: u264 })(Fq.add, (x, y) => x.add(y)); +equivalent({ from: [f, f], to: u264 })(Fq.sub, (x, y) => x.sub(y)); +equivalent({ from: [f], to: u264 })(Fq.negate, (x) => x.neg()); +equivalent({ from: [f, f], to: u264 })(Fq.mul, (x, y) => x.mul(y)); +equivalent({ from: [f], to: f })( + (x) => Fq.inverse(x) ?? throwError('division by 0'), + (x) => x.inv() +); +equivalent({ from: [f, f], to: f })( + (x, y) => Fq.div(x, y) ?? throwError('division by 0'), + (x, y) => x.div(y) +); + +// equality with a constant +equivalent({ from: [f, first(f)], to: bool })( + (x, y) => x === y, + (x, y) => x.equals(y) +); +equivalent({ from: [f, f], to: unit })( + (x, y) => x === y || throwError('not equal'), + (x, y) => x.assertEquals(y) +); +equivalent({ from: [f, first(u264)], to: unit })( + (x, y) => x < y || throwError('not less than'), + (x, y) => x.assertLessThan(y) +); + +// toBits / fromBits +equivalent({ from: [f], to: f })( + (x) => x, + (x) => { + let bits = x.toBits(); + expect(bits.length).toEqual(255); + return ForeignScalar.fromBits(bits); + } +); + +// scalar shift in foreign field arithmetic vs in the exponent + +let scalarShift = Fq.mod(1n + 2n ** 255n); +let oneHalf = Fq.inverse(2n)!; + +function unshift(s: ForeignField) { + return s.sub(scalarShift).assertAlmostReduced().mul(oneHalf); +} +function scaleShifted(point: Group, shiftedScalar: Scalar) { + let oneHalfGroup = point.scale(oneHalf); + let shiftGroup = oneHalfGroup.scale(scalarShift); + return oneHalfGroup.scale(shiftedScalar).sub(shiftGroup); +} + +let scalarBigint = Fq.random(); +let pointBigint = Pallas.toAffine(Pallas.scale(Pallas.one, scalarBigint)); + +// perform a "scalar unshift" in foreign field arithmetic, +// then convert to scalar from bits (which shifts it back) and scale a point by the scalar +function main0() { + let ffScalar = Provable.witness( + ForeignScalar.provable, + () => new ForeignScalar(scalarBigint) + ); + let bitsUnshifted = unshift(ffScalar).toBits(); + let scalar = Scalar.fromBits(bitsUnshifted); + + let generator = Provable.witness(Group, () => Group.generator); + let point = generator.scale(scalar); + point.assertEquals(Group(pointBigint)); +} + +// go directly from foreign scalar to scalar and perform a shifted scale +// = same end result as main0 +function main1() { + let ffScalar = Provable.witness( + ForeignScalar.provable, + () => new ForeignScalar(scalarBigint) + ); + let bits = ffScalar.toBits(); + let scalarShifted = Scalar.fromBits(bits); + + let generator = Provable.witness(Group, () => Group.generator); + let point = scaleShifted(generator, scalarShifted); + point.assertEquals(Group(pointBigint)); +} + +// check provable and non-provable versions are correct +main0(); +main1(); +await Provable.runAndCheck(main0); +await Provable.runAndCheck(main1); + +// using foreign field arithmetic should result in much fewer constraints +let { rows: rows0 } = await Provable.constraintSystem(main0); +let { rows: rows1 } = await Provable.constraintSystem(main1); +expect(rows0 + 100).toBeLessThan(rows1); + +// test with proving + +class Main extends Circuit { + @circuitMain + static main() { + main0(); + } +} + +let kp = await Main.generateKeypair(); + +let cs = kp.constraintSystem(); +assert( + cs.length === 1 << 13, + `should have ${cs.length} = 2^13 rows, the smallest supported number` +); + +let proof = await Main.prove([], [], kp); + +let ok = await Main.verify([], kp.verificationKey(), proof); +assert(ok, 'proof should verify'); diff --git a/src/lib/provable/test/group.test.ts b/src/lib/provable/test/group.test.ts new file mode 100644 index 0000000000..fce7372f35 --- /dev/null +++ b/src/lib/provable/test/group.test.ts @@ -0,0 +1,376 @@ +import { Bool, Group, Scalar, Provable } from 'o1js'; + +describe('group', () => { + let g = Group({ + x: -1, + y: 2, + }); + + describe('Inside circuit', () => { + describe('group membership', () => { + it('valid element does not throw', async () => { + await Provable.runAndCheck(() => { + Provable.witness(Group, () => g); + }); + }); + + it('valid element does not throw', async () => { + await Provable.runAndCheck(() => { + Provable.witness(Group, () => Group.generator); + }); + }); + + it('Group.zero element does not throw', async () => { + await Provable.runAndCheck(() => { + Provable.witness(Group, () => Group.zero); + }); + }); + + it('invalid group element throws', async () => { + await expect( + Provable.runAndCheck(() => { + Provable.witness(Group, () => Group({ x: 2, y: 2 })); + }) + ).rejects.toThrow(); + }); + }); + + describe('add', () => { + it('g+g does not throw', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => g); + const y = Provable.witness(Group, () => g); + x.add(y); + }); + }); + + it('g+zero = g', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => g); + const zero = Provable.witness(Group, () => Group.zero); + x.add(zero).assertEquals(x); + }); + }); + + it('zero+g = g', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => g); + const zero = Provable.witness(Group, () => Group.zero); + zero.add(x).assertEquals(x); + }); + }); + + it('g+(-g) = zero', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => g); + const zero = Provable.witness(Group, () => Group.zero); + x.add(x.neg()).assertEquals(zero); + }); + }); + + it('(-g)+g = zero', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => g); + const zero = Provable.witness(Group, () => Group.zero); + x.neg().add(x).assertEquals(zero); + }); + }); + + it('zero + zero = zero', async () => { + await Provable.runAndCheck(() => { + const zero = Provable.witness(Group, () => Group.zero); + zero.add(zero).assertEquals(zero); + }); + }); + }); + + describe('sub', () => { + it('g-g does not throw', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => g); + const y = Provable.witness(Group, () => g); + x.sub(y); + }); + }); + + it('g-zero = g', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => g); + const zero = Provable.witness(Group, () => Group.zero); + x.sub(zero).assertEquals(x); + }); + }); + + it('zero - g = -g', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => g); + const zero = Provable.witness(Group, () => Group.zero); + zero.sub(x).assertEquals(x.neg()); + }); + }); + + it('zero - zero = zero', async () => { + await Provable.runAndCheck(() => { + const zero = Provable.witness(Group, () => Group.zero); + zero.sub(zero).assertEquals(zero); + }); + }); + }); + + describe('neg', () => { + it('neg(g) not to throw', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => g); + x.neg(); + }); + }); + + it('neg(zero) = zero', async () => { + await Provable.runAndCheck(() => { + const zero = Provable.witness(Group, () => Group.zero); + zero.neg().assertEquals(zero); + }); + }); + }); + + describe('scale', () => { + it('scaling with random Scalar does not throw', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => g); + x.scale(Scalar.random()); + }); + }); + + it('x*g+y*g = (x+y)*g', async () => { + await Provable.runAndCheck(() => { + const x = Scalar.from(2); + const y = Scalar.from(3); + const left = g.scale(x).add(g.scale(y)); + const right = g.scale(x.add(y)); + left.assertEquals(right); + }); + }); + + it('x*(y*g) = (x*y)*g', async () => { + await Provable.runAndCheck(() => { + const x = Scalar.from(2); + const y = Scalar.from(3); + const left = g.scale(y).scale(x); + const right = g.scale(y.mul(x)); + left.assertEquals(right); + }); + }); + }); + + describe('equals', () => { + it('should equal true with same group', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => Group.generator); + let isEqual = x.equals(Group.generator); + Provable.asProver(() => { + expect(isEqual.toBoolean()).toEqual(true); + }); + }); + }); + + it('should equal false with different group', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => Group.generator); + let isEqual = x.equals(g); + Provable.asProver(() => { + expect(isEqual.toBoolean()).toEqual(false); + }); + }); + }); + }); + + describe('assertEquals', () => { + it('should not throw with same group', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => Group.generator); + x.assertEquals(Group.generator); + }); + }); + + it('should throw with different group', async () => { + await expect( + Provable.runAndCheck(() => { + const x = Provable.witness(Group, () => Group.generator); + x.assertEquals(g); + }) + ).rejects.toThrow(); + }); + }); + + describe('toJSON', () => { + it('fromJSON(g.toJSON) should be the same as g', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness( + Group, + () => Group.fromJSON(Group.generator.toJSON())! + ); + Provable.asProver(() => { + expect(x.equals(Group.generator).toBoolean()).toEqual(true); + }); + }); + }); + }); + }); + + describe('Outside circuit', () => { + describe('neg', () => { + it('neg not to throw', () => { + expect(() => { + g.neg(); + }).not.toThrow(); + }); + + it('zero.neg = zero', () => { + expect(() => { + const zero = Group.zero; + zero.neg().assertEquals(zero); + }).not.toThrow(); + }); + }); + + describe('add', () => { + it('(-1,2)+(-1,2) does not throw', () => { + expect(() => { + g.add(g); + }).not.toThrow(); + }); + + it('g + zero = g', () => { + expect(() => { + const zero = Group.zero; + g.add(zero).assertEquals(g); + }).not.toThrow(); + }); + + it('zero + g = g', () => { + expect(() => { + const zero = Group.zero; + zero.add(g).assertEquals(g); + }).not.toThrow(); + }); + + it('g + (-g) = zero', () => { + expect(() => { + const zero = Group.zero; + g.add(g.neg()).assertEquals(zero); + }).not.toThrow(); + }); + + it('(-g) + g = zero', () => { + expect(() => { + const zero = Group.zero; + g.neg().add(g).assertEquals(zero); + }).not.toThrow(); + }); + + it('zero + zero = zero', () => { + expect(() => { + const zero = Group.zero; + zero.add(zero).assertEquals(zero); + }).not.toThrow(); + }); + }); + + describe('sub', () => { + it('generator-(-1,2) does not throw', () => { + expect(() => { + Group.generator.sub(g); + }).not.toThrow(); + }); + + it('g - zero = g', () => { + expect(() => { + const zero = Group.zero; + g.sub(zero).assertEquals(g); + }).not.toThrow(); + }); + + it('zero - g = -g', () => { + expect(() => { + const zero = Group.zero; + zero.sub(g).assertEquals(g.neg()); + }).not.toThrow(); + }); + + it('zero - zero = -zero', () => { + expect(() => { + const zero = Group.zero; + zero.sub(zero).assertEquals(zero); + }).not.toThrow(); + }); + }); + + describe('scale', () => { + it('scaling with random Scalar does not throw', () => { + expect(() => { + g.scale(Scalar.random()); + }).not.toThrow(); + }); + + it('x*g+y*g = (x+y)*g', () => { + const x = Scalar.from(2); + const y = Scalar.from(3); + const left = g.scale(x).add(g.scale(y)); + const right = g.scale(x.add(y)); + expect(left).toEqual(right); + }); + + it('x*(y*g) = (x*y)*g', () => { + const x = Scalar.from(2); + const y = Scalar.from(3); + const left = g.scale(y).scale(x); + const right = g.scale(y.mul(x)); + expect(left).toEqual(right); + }); + }); + + describe('equals', () => { + it('should equal true with same group', () => { + expect(g.equals(g)).toEqual(Bool(true)); + }); + + it('should equal false with different group', () => { + expect(g.equals(Group.generator)).toEqual(Bool(false)); + }); + }); + + describe('toJSON', () => { + it("fromJSON('1','1') should be the same as Group(1,1)", () => { + const x = Group.fromJSON({ x: -1, y: 2 }); + expect(x).toEqual(g); + }); + }); + }); + + describe('Variable/Constant circuit equality ', () => { + it('add', async () => { + await Provable.runAndCheck(() => { + let y = Provable.witness(Group, () => g).add( + Provable.witness(Group, () => Group.generator) + ); + let z = g.add(Group.generator); + y.assertEquals(z); + }); + }); + + it('sub', () => { + let y = Provable.witness(Group, () => g).sub( + Provable.witness(Group, () => Group.generator) + ); + let z = g.sub(Group.generator); + y.assertEquals(z); + }); + + it('sub', () => { + let y = Provable.witness(Group, () => g).assertEquals( + Provable.witness(Group, () => g) + ); + g.assertEquals(g); + }); + }); +}); diff --git a/src/lib/group.unit-test.ts b/src/lib/provable/test/group.unit-test.ts similarity index 86% rename from src/lib/group.unit-test.ts rename to src/lib/provable/test/group.unit-test.ts index 5a7102f078..a6d3205b5d 100644 --- a/src/lib/group.unit-test.ts +++ b/src/lib/provable/test/group.unit-test.ts @@ -1,7 +1,8 @@ -import { Group } from './core.js'; -import { test, Random } from './testing/property.js'; -import { Provable } from './provable.js'; -import { Poseidon } from '../provable/poseidon-bigint.js'; +import { Group } from '../wrapped.js'; +import { test, Random } from '../../testing/property.js'; +import { Provable } from '../provable.js'; +import { Poseidon } from '../../../mina-signer/src/poseidon-bigint.js'; +import { runAndCheckSync } from '../core/provable-context.js'; console.log('group consistency tests'); @@ -54,7 +55,7 @@ function run( ) { let result_out_circuit = f(g1, g2); - Provable.runAndCheck(() => { + runAndCheckSync(() => { let result_in_circuit = f( Provable.witness(Group, () => g1), Provable.witness(Group, () => g2) diff --git a/src/lib/provable/test/int.test.ts b/src/lib/provable/test/int.test.ts new file mode 100644 index 0000000000..30a05b9728 --- /dev/null +++ b/src/lib/provable/test/int.test.ts @@ -0,0 +1,2688 @@ +import { + Provable, + Int64, + UInt64, + UInt32, + UInt8, + Field, + Bool, + Sign, +} from 'o1js'; + +describe('int', () => { + const NUMBERMAX = 2 ** 53 - 1; // JavaScript numbers can only safely store integers in the range -(2^53 − 1) to 2^53 − 1 + + describe('Int64', () => { + describe('toString', () => { + it('should be the same as Field(0)', async () => { + const int = new Int64(UInt64.zero, Sign.one); + const field = Field(0); + expect(int.toString()).toEqual(field.toString()); + }); + + it('should be -1', async () => { + const int = new Int64(UInt64.one).neg(); + expect(int.toString()).toEqual('-1'); + }); + + it('should be the same as 2^53-1', async () => { + const int = Int64.fromField(Field(String(NUMBERMAX))); + const field = Field(String(NUMBERMAX)); + expect(int.toString()).toEqual(field.toString()); + }); + }); + + describe('zero', () => { + it('should be the same as Field(0)', async () => { + expect(Int64.zero.magnitude.value).toEqual(Field(0)); + }); + }); + + describe('fromUnsigned', () => { + it('should be the same as UInt64.zero', async () => { + expect(new Int64(UInt64.zero, Sign.one)).toEqual( + Int64.fromUnsigned(UInt64.zero) + ); + }); + + it('should be the same as UInt64.MAXINT', async () => { + expect(Int64.from((1n << 64n) - 1n)).toEqual( + Int64.fromUnsigned(UInt64.MAXINT()) + ); + }); + }); + + describe('neg', () => { + it('neg(1)=-1', () => { + const int = Int64.one; + expect(int.neg().toField()).toEqual(Field(-1)); + }); + it('neg(2^53-1)=-2^53-1', () => { + const int = Int64.from(NUMBERMAX); + expect(int.neg().toString()).toEqual(`${-NUMBERMAX}`); + }); + }); + + describe('add', () => { + it('1+1=2', () => { + expect(Int64.one.add(Int64.from('1')).toString()).toEqual('2'); + }); + + it('5000+(-4000)=1000', () => { + expect( + Int64.from(5000) + .add(Int64.fromField(Field(-4000))) + .toString() + ).toEqual('1000'); + }); + + it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { + const value = ((1n << 64n) - 2n) / 2n; + expect( + Int64.from(value).add(Int64.from(value)).add(Int64.one).toString() + ).toEqual(UInt64.MAXINT().toString()); + }); + + it('should throw on overflow', () => { + expect(() => { + Int64.from(1n << 64n); + }).toThrow(); + expect(() => { + Int64.from(-(1n << 64n)); + }).toThrow(); + expect(() => { + Int64.from(100).add(1n << 64n); + }).toThrow(); + expect(() => { + Int64.from(100).sub('1180591620717411303424'); + }).toThrow(); + expect(() => { + Int64.from(100).mul(UInt64.from(1n << 100n)); + }).toThrow(); + }); + + // TODO - should we make these throw? + // These are edge cases, where one of two inputs is out of the Int64 range, + // but the result of an operation with a proper Int64 moves it into the range. + // They would only get caught if we'd also check the range in the Int64 / UInt64 constructors, + // which breaks out current practice of having a dumb constructor that only stores variables + it.skip('operations should throw on overflow of any input', () => { + expect(() => { + new Int64(new UInt64(1n << 64n)).sub(1); + }).toThrow(); + expect(() => { + new Int64(new UInt64(-(1n << 64n))).add(5); + }).toThrow(); + expect(() => { + Int64.from(20).sub(new UInt64((1n << 64n) + 10n)); + }).toThrow(); + expect(() => { + Int64.from(6).add(new UInt64(-(1n << 64n) - 5n)); + }).toThrow(); + }); + + it('should throw on overflow addition', () => { + expect(() => { + Int64.from((1n << 64n) - 1n).add(1); + }).toThrow(); + expect(() => { + Int64.one.add((1n << 64n) - 1n); + }).toThrow(); + }); + it('should not throw on non-overflowing addition', () => { + expect(() => { + Int64.from((1n << 64n) - 1n).add(Int64.zero); + }).not.toThrow(); + }); + }); + + describe('sub', () => { + it('1-1=0', () => { + expect(Int64.one.sub(Int64.from(1)).toString()).toEqual('0'); + }); + + it('10000-5000=5000', () => { + expect( + Int64.fromField(Field(10000)).sub(Int64.from('5000')).toString() + ).toEqual('5000'); + }); + + it('0-1=-1', () => { + expect(Int64.zero.sub(Int64.one).toString()).toEqual('-1'); + }); + + it('(0-MAXINT) subs to -MAXINT', () => { + expect(Int64.zero.sub(UInt64.MAXINT()).toString()).toEqual( + '-' + UInt64.MAXINT().toString() + ); + }); + }); + + describe('toFields', () => { + it('toFields(1) should be the same as [Field(1), Field(1)]', () => { + expect(Int64.toFields(Int64.one)).toEqual([Field(1), Field(1)]); + }); + + it('toFields(2^53-1) should be the same as Field(2^53-1)', () => { + expect(Int64.toFields(Int64.from(NUMBERMAX))).toEqual([ + Field(String(NUMBERMAX)), + Field(1), + ]); + }); + }); + describe('fromFields', () => { + it('fromFields([1, 1]) should be the same as Int64.one', () => { + expect(Int64.fromFields([Field(1), Field(1)])).toEqual(Int64.one); + }); + + it('fromFields(2^53-1) should be the same as Field(2^53-1)', () => { + expect(Int64.fromFields([Field(String(NUMBERMAX)), Field(1)])).toEqual( + Int64.from(NUMBERMAX) + ); + }); + }); + + describe('mul / div / mod', () => { + it('mul, div and mod work', () => { + // 2 ** 6 === 64 + let x = Int64.fromField(Field(2)) + .mul(2) + .mul('2') + .mul(2n) + .mul(UInt32.from(2)) + .mul(UInt64.from(2)); + expect(`${x}`).toBe('64'); + + // 64 * (-64) === -64**2 + let y = Int64.from(-64); + expect(`${x.mul(y)}`).toEqual(`${-(64 ** 2)}`); + // (-64) // 64 === -1 + expect(y.div(x).toString()).toEqual('-1'); + // (-64) // 65 === 0 + expect(y.div(65).toString()).toEqual('0'); + // 64 % 3 === 1 + expect(x.mod(3).toString()).toEqual('1'); + // (-64) % 3 === 2 + expect(y.mod(3).toString()).toEqual('2'); + }); + }); + }); + + describe('UInt64', () => { + describe('Inside circuit', () => { + describe('add', () => { + it('1+1=2', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(UInt64, () => new UInt64(1)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.add(y).assertEquals(new UInt64(2)); + }); + }); + + it('5000+5000=10000', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(5000)); + const y = Provable.witness(UInt64, () => new UInt64(5000)); + x.add(y).assertEquals(new UInt64(10000)); + }); + }); + + it('(MAXINT/2+MAXINT/2) adds to MAXINT', async () => { + const n = ((1n << 64n) - 2n) / 2n; + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(n)); + const y = Provable.witness(UInt64, () => new UInt64(n)); + x.add(y).add(1).assertEquals(UInt64.MAXINT()); + }); + }); + + it('should throw on overflow addition', async () => { + await expect( + Provable.runAndCheck(() => { + const x = Provable.witness(UInt64, () => UInt64.MAXINT()); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.add(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('sub', () => { + it('1-1=0', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.sub(y).assertEquals(new UInt64(0)); + }); + }); + + it('10000-5000=5000', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(10000)); + const y = Provable.witness(UInt64, () => new UInt64(5000)); + x.sub(y).assertEquals(new UInt64(5000)); + }); + }); + + it('should throw on sub if results in negative number', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(0)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.sub(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('mul', () => { + it('1x2=2', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1)); + const y = Provable.witness(UInt64, () => new UInt64(2)); + x.mul(y).assertEquals(new UInt64(2)); + }); + }); + + it('1x0=0', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1)); + const y = Provable.witness(UInt64, () => new UInt64(0)); + x.mul(y).assertEquals(new UInt64(0)); + }); + }); + + it('1000x1000=1000000', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1000)); + const y = Provable.witness(UInt64, () => new UInt64(1000)); + x.mul(y).assertEquals(new UInt64(1000000)); + }); + }); + + it('MAXINTx1=MAXINT', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => UInt64.MAXINT()); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.mul(y).assertEquals(UInt64.MAXINT()); + }); + }); + + it('should throw on overflow multiplication', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => UInt64.MAXINT()); + const y = Provable.witness(UInt64, () => new UInt64(2)); + x.mul(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('div', () => { + it('2/1=2', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(2)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.div(y).assertEquals(new UInt64(2)); + }); + }); + + it('0/1=0', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(0)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.div(y).assertEquals(new UInt64(0)); + }); + }); + + it('2000/1000=2', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(2000)); + const y = Provable.witness(UInt64, () => new UInt64(1000)); + x.div(y).assertEquals(new UInt64(2)); + }); + }); + + it('MAXINT/1=MAXINT', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => UInt64.MAXINT()); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.div(y).assertEquals(UInt64.MAXINT()); + }); + }); + + it('should throw on division by zero', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => UInt64.MAXINT()); + const y = Provable.witness(UInt64, () => new UInt64(0)); + x.div(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('mod', () => { + it('1%1=0', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.mod(y).assertEquals(new UInt64(0)); + }); + }); + + it('500%32=20', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(500)); + const y = Provable.witness(UInt64, () => new UInt64(32)); + x.mod(y).assertEquals(new UInt64(20)); + }); + }); + + it('MAXINT%7=1', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => UInt64.MAXINT()); + const y = Provable.witness(UInt64, () => new UInt64(7)); + x.mod(y).assertEquals(new UInt64(1)); + }); + }); + + it('should throw on mod by zero', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => UInt64.MAXINT()); + const y = Provable.witness(UInt64, () => new UInt64(0)); + x.mod(y).assertEquals(new UInt64(1)); + }) + ).rejects.toThrow(); + }); + }); + + describe('assertLt', () => { + it('1<2=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1)); + const y = Provable.witness(UInt64, () => new UInt64(2)); + x.assertLessThan(y); + }); + }); + + it('1<1=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.assertLessThan(y); + }) + ).rejects.toThrow(); + }); + + it('2<1=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(2)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.assertLessThan(y); + }) + ).rejects.toThrow(); + }); + + it('1000<100000=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1000)); + const y = Provable.witness(UInt64, () => new UInt64(100000)); + x.assertLessThan(y); + }); + }); + + it('100000<1000=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(100000)); + const y = Provable.witness(UInt64, () => new UInt64(1000)); + x.assertLessThan(y); + }) + ).rejects.toThrow(); + }); + + it('MAXINT { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => UInt64.MAXINT()); + const y = Provable.witness(UInt64, () => UInt64.MAXINT()); + x.assertLessThan(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('assertLte', () => { + it('1<=1=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.assertLessThanOrEqual(y); + }); + }); + + it('2<=1=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(2)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.assertLessThanOrEqual(y); + }) + ).rejects.toThrow(); + }); + + it('1000<=100000=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1000)); + const y = Provable.witness(UInt64, () => new UInt64(100000)); + x.assertLessThanOrEqual(y); + }); + }); + + it('100000<=1000=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(100000)); + const y = Provable.witness(UInt64, () => new UInt64(1000)); + x.assertLessThanOrEqual(y); + }) + ).rejects.toThrow(); + }); + + it('MAXINT<=MAXINT=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => UInt64.MAXINT()); + const y = Provable.witness(UInt64, () => UInt64.MAXINT()); + x.assertLessThanOrEqual(y); + }); + }); + }); + + describe('assertGreaterThan', () => { + it('2>1=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(2)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.assertGreaterThan(y); + }); + }); + + it('1>1=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.assertGreaterThan(y); + }) + ).rejects.toThrow(); + }); + + it('1>2=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1)); + const y = Provable.witness(UInt64, () => new UInt64(2)); + x.assertGreaterThan(y); + }) + ).rejects.toThrow(); + }); + + it('100000>1000=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(100000)); + const y = Provable.witness(UInt64, () => new UInt64(1000)); + x.assertGreaterThan(y); + }); + }); + + it('1000>100000=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1000)); + const y = Provable.witness(UInt64, () => new UInt64(100000)); + x.assertGreaterThan(y); + }) + ).rejects.toThrow(); + }); + + it('MAXINT>MAXINT=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => UInt64.MAXINT()); + const y = Provable.witness(UInt64, () => UInt64.MAXINT()); + x.assertGreaterThan(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('assertGreaterThanOrEqual', () => { + it('1<=1=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.assertGreaterThanOrEqual(y); + }); + }); + + it('1>=2=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1)); + const y = Provable.witness(UInt64, () => new UInt64(2)); + x.assertGreaterThanOrEqual(y); + }) + ).rejects.toThrow(); + }); + + it('100000>=1000=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(100000)); + const y = Provable.witness(UInt64, () => new UInt64(1000)); + x.assertGreaterThanOrEqual(y); + }); + }); + + it('1000>=100000=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => new UInt64(1000)); + const y = Provable.witness(UInt64, () => new UInt64(100000)); + x.assertGreaterThanOrEqual(y); + }) + ).rejects.toThrow(); + }); + + it('MAXINT>=MAXINT=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => UInt64.MAXINT()); + const y = Provable.witness(UInt64, () => UInt64.MAXINT()); + x.assertGreaterThanOrEqual(y); + }); + }); + }); + + describe('from() ', () => { + describe('fromNumber()', () => { + it('should be the same as Field(1)', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => UInt64.from(1)); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.assertEquals(y); + }); + }); + + it('should be the same as 2^53-1', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => UInt64.from(NUMBERMAX)); + const y = Provable.witness( + UInt64, + () => new UInt64(String(NUMBERMAX)) + ); + x.assertEquals(y); + }); + }); + }); + describe('fromString()', () => { + it('should be the same as Field(1)', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => UInt64.from('1')); + const y = Provable.witness(UInt64, () => new UInt64(1)); + x.assertEquals(y); + }); + }); + + it('should be the same as 2^53-1', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt64, () => + UInt64.from(String(NUMBERMAX)) + ); + const y = Provable.witness( + UInt64, + () => new UInt64(String(NUMBERMAX)) + ); + x.assertEquals(y); + }); + }); + }); + }); + }); + + describe('Outside of circuit', () => { + describe('add', () => { + it('1+1=2', () => { + expect(new UInt64(1).add(1).toString()).toEqual('2'); + }); + + it('5000+5000=10000', () => { + expect(new UInt64(5000).add(5000).toString()).toEqual('10000'); + }); + + it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { + const value = ((1n << 64n) - 2n) / 2n; + expect( + new UInt64(value) + .add(new UInt64(value)) + .add(new UInt64(1)) + .toString() + ).toEqual(UInt64.MAXINT().toString()); + }); + + it('should throw on overflow addition', () => { + expect(() => { + UInt64.MAXINT().add(1); + }).toThrow(); + }); + }); + + describe('sub', () => { + it('1-1=0', () => { + expect(new UInt64(1).sub(1).toString()).toEqual('0'); + }); + + it('10000-5000=5000', () => { + expect(new UInt64(10000).sub(5000).toString()).toEqual('5000'); + }); + + it('should throw on sub if results in negative number', () => { + expect(() => { + UInt64.from(0).sub(1); + }).toThrow(); + }); + }); + + describe('mul', () => { + it('1x2=2', () => { + expect(new UInt64(1).mul(2).toString()).toEqual('2'); + }); + + it('1x0=0', () => { + expect(new UInt64(1).mul(0).toString()).toEqual('0'); + }); + + it('1000x1000=1000000', () => { + expect(new UInt64(1000).mul(1000).toString()).toEqual('1000000'); + }); + + it('MAXINTx1=MAXINT', () => { + expect(UInt64.MAXINT().mul(1).toString()).toEqual( + UInt64.MAXINT().toString() + ); + }); + + it('should throw on overflow multiplication', () => { + expect(() => { + UInt64.MAXINT().mul(2); + }).toThrow(); + }); + }); + + describe('div', () => { + it('2/1=2', () => { + expect(new UInt64(2).div(1).toString()).toEqual('2'); + }); + + it('0/1=0', () => { + expect(new UInt64(0).div(1).toString()).toEqual('0'); + }); + + it('2000/1000=2', () => { + expect(new UInt64(2000).div(1000).toString()).toEqual('2'); + }); + + it('MAXINT/1=MAXINT', () => { + expect(UInt64.MAXINT().div(1).toString()).toEqual( + UInt64.MAXINT().toString() + ); + }); + + it('should throw on division by zero', () => { + expect(() => { + UInt64.MAXINT().div(0); + }).toThrow(); + }); + }); + + describe('mod', () => { + it('1%1=0', () => { + expect(new UInt64(1).mod(1).toString()).toEqual('0'); + }); + + it('500%32=20', () => { + expect(new UInt64(500).mod(32).toString()).toEqual('20'); + }); + + it('MAXINT%7=1', () => { + expect(UInt64.MAXINT().mod(7).toString()).toEqual('1'); + }); + + it('should throw on mod by zero', () => { + expect(() => { + UInt64.MAXINT().mod(0); + }).toThrow(); + }); + }); + + describe('lt', () => { + it('1<2=true', () => { + expect(new UInt64(1).lessThan(new UInt64(2))).toEqual(Bool(true)); + }); + + it('1<1=false', () => { + expect(new UInt64(1).lessThan(new UInt64(1))).toEqual(Bool(false)); + }); + + it('2<1=false', () => { + expect(new UInt64(2).lessThan(new UInt64(1))).toEqual(Bool(false)); + }); + + it('1000<100000=true', () => { + expect(new UInt64(1000).lessThan(new UInt64(100000))).toEqual( + Bool(true) + ); + }); + + it('100000<1000=false', () => { + expect(new UInt64(100000).lessThan(new UInt64(1000))).toEqual( + Bool(false) + ); + }); + + it('MAXINT { + expect(UInt64.MAXINT().lessThan(UInt64.MAXINT())).toEqual( + Bool(false) + ); + }); + }); + + describe('lte', () => { + it('1<=1=true', () => { + expect(new UInt64(1).lessThanOrEqual(new UInt64(1))).toEqual( + Bool(true) + ); + }); + + it('2<=1=false', () => { + expect(new UInt64(2).lessThanOrEqual(new UInt64(1))).toEqual( + Bool(false) + ); + }); + + it('1000<=100000=true', () => { + expect(new UInt64(1000).lessThanOrEqual(new UInt64(100000))).toEqual( + Bool(true) + ); + }); + + it('100000<=1000=false', () => { + expect(new UInt64(100000).lessThanOrEqual(new UInt64(1000))).toEqual( + Bool(false) + ); + }); + + it('MAXINT<=MAXINT=true', () => { + expect(UInt64.MAXINT().lessThanOrEqual(UInt64.MAXINT())).toEqual( + Bool(true) + ); + }); + }); + + describe('assertLessThanOrEqual', () => { + it('1<=1=true', () => { + expect(() => { + new UInt64(1).assertLessThanOrEqual(new UInt64(1)); + }).not.toThrow(); + }); + + it('2<=1=false', () => { + expect(() => { + new UInt64(2).assertLessThanOrEqual(new UInt64(1)); + }).toThrow(); + }); + + it('1000<=100000=true', () => { + expect(() => { + new UInt64(1000).assertLessThanOrEqual(new UInt64(100000)); + }).not.toThrow(); + }); + + it('100000<=1000=false', () => { + expect(() => { + new UInt64(100000).assertLessThanOrEqual(new UInt64(1000)); + }).toThrow(); + }); + + it('MAXINT<=MAXINT=true', () => { + expect(() => { + UInt64.MAXINT().assertLessThanOrEqual(UInt64.MAXINT()); + }).not.toThrow(); + }); + }); + + describe('greaterThan', () => { + it('2>1=true', () => { + expect(new UInt64(2).greaterThan(new UInt64(1))).toEqual(Bool(true)); + }); + + it('1>1=false', () => { + expect(new UInt64(1).greaterThan(new UInt64(1))).toEqual(Bool(false)); + }); + + it('1>2=false', () => { + expect(new UInt64(1).greaterThan(new UInt64(2))).toEqual(Bool(false)); + }); + + it('100000>1000=true', () => { + expect(new UInt64(100000).greaterThan(new UInt64(1000))).toEqual( + Bool(true) + ); + }); + + it('1000>100000=false', () => { + expect(new UInt64(1000).greaterThan(new UInt64(100000))).toEqual( + Bool(false) + ); + }); + + it('MAXINT>MAXINT=false', () => { + expect(UInt64.MAXINT().greaterThan(UInt64.MAXINT())).toEqual( + Bool(false) + ); + }); + }); + + describe('greaterThanOrEqual', () => { + it('2>=1=true', () => { + expect(new UInt64(2).greaterThanOrEqual(new UInt64(1))).toEqual( + Bool(true) + ); + }); + + it('1>=1=true', () => { + expect(new UInt64(1).greaterThanOrEqual(new UInt64(1))).toEqual( + Bool(true) + ); + }); + + it('1>=2=false', () => { + expect(new UInt64(1).greaterThanOrEqual(new UInt64(2))).toEqual( + Bool(false) + ); + }); + + it('100000>=1000=true', () => { + expect( + new UInt64(100000).greaterThanOrEqual(new UInt64(1000)) + ).toEqual(Bool(true)); + }); + + it('1000>=100000=false', () => { + expect( + new UInt64(1000).greaterThanOrEqual(new UInt64(100000)) + ).toEqual(Bool(false)); + }); + + it('MAXINT>=MAXINT=true', () => { + expect(UInt64.MAXINT().greaterThanOrEqual(UInt64.MAXINT())).toEqual( + Bool(true) + ); + }); + }); + + describe('assertGreaterThan', () => { + it('1>1=false', () => { + expect(() => { + new UInt64(1).assertGreaterThan(new UInt64(1)); + }).toThrow(); + }); + + it('2>1=true', () => { + expect(() => { + new UInt64(2).assertGreaterThan(new UInt64(1)); + }).not.toThrow(); + }); + + it('1000>100000=false', () => { + expect(() => { + new UInt64(1000).assertGreaterThan(new UInt64(100000)); + }).toThrow(); + }); + + it('100000>1000=true', () => { + expect(() => { + new UInt64(100000).assertGreaterThan(new UInt64(1000)); + }).not.toThrow(); + }); + + it('MAXINT>MAXINT=false', () => { + expect(() => { + UInt64.MAXINT().assertGreaterThan(UInt64.MAXINT()); + }).toThrow(); + }); + }); + + describe('assertGreaterThanOrEqual', () => { + it('1>=1=true', () => { + expect(() => { + new UInt64(1).assertGreaterThanOrEqual(new UInt64(1)); + }).not.toThrow(); + }); + + it('2>=1=true', () => { + expect(() => { + new UInt64(2).assertGreaterThanOrEqual(new UInt64(1)); + }).not.toThrow(); + }); + + it('1000>=100000=false', () => { + expect(() => { + new UInt64(1000).assertGreaterThanOrEqual(new UInt64(100000)); + }).toThrow(); + }); + + it('100000>=1000=true', () => { + expect(() => { + new UInt64(100000).assertGreaterThanOrEqual(new UInt64(1000)); + }).not.toThrow(); + }); + + it('MAXINT>=MAXINT=true', () => { + expect(() => { + UInt64.MAXINT().assertGreaterThanOrEqual(UInt64.MAXINT()); + }).not.toThrow(); + }); + }); + + describe('toString()', () => { + it('should be the same as Field(0)', async () => { + const uint64 = new UInt64(0); + const field = Field(0); + expect(uint64.toString()).toEqual(field.toString()); + }); + it('should be the same as 2^53-1', async () => { + const uint64 = new UInt64(String(NUMBERMAX)); + const field = Field(String(NUMBERMAX)); + expect(uint64.toString()).toEqual(field.toString()); + }); + }); + + describe('check()', () => { + it('should pass checking a MAXINT', () => { + expect(() => { + UInt64.check(UInt64.MAXINT()); + }).not.toThrow(); + }); + + it('should throw checking over MAXINT', () => { + const aboveMax = new UInt64(1); + aboveMax.value = Field(1n << 64n); + expect(() => { + UInt64.check(aboveMax); + }).toThrow(); + }); + }); + + describe('from() ', () => { + describe('fromNumber()', () => { + it('should be the same as Field(1)', () => { + const uint = UInt64.from(1); + expect(uint.value).toEqual(new UInt64(1).value); + }); + + it('should be the same as 2^53-1', () => { + const uint = UInt64.from(NUMBERMAX); + expect(uint.value).toEqual(Field(String(NUMBERMAX))); + }); + }); + describe('fromString()', () => { + it('should be the same as Field(1)', () => { + const uint = UInt64.from('1'); + expect(uint.value).toEqual(new UInt64(1).value); + }); + + it('should be the same as 2^53-1', () => { + const uint = UInt64.from(String(NUMBERMAX)); + expect(uint.value).toEqual(Field(String(NUMBERMAX))); + }); + }); + }); + }); + }); + + describe('UInt32', () => { + const NUMBERMAX = 2 ** 32 - 1; + + describe('Inside circuit', () => { + describe('add', () => { + it('1+1=2', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.add(y).assertEquals(new UInt32(2)); + }); + }); + + it('5000+5000=10000', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(5000)); + const y = Provable.witness(UInt32, () => new UInt32(5000)); + x.add(y).assertEquals(new UInt32(10000)); + }); + }); + + it('(MAXINT/2+MAXINT/2) adds to MAXINT', async () => { + const n = ((1n << 32n) - 2n) / 2n; + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(n)); + const y = Provable.witness(UInt32, () => new UInt32(n)); + x.add(y).add(1).assertEquals(UInt32.MAXINT()); + }); + }); + + it('should throw on overflow addition', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.MAXINT()); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.add(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('sub', () => { + it('1-1=0', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.sub(y).assertEquals(new UInt32(0)); + }); + }); + + it('10000-5000=5000', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(10000)); + const y = Provable.witness(UInt32, () => new UInt32(5000)); + x.sub(y).assertEquals(new UInt32(5000)); + }); + }); + + it('should throw on sub if results in negative number', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(0)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.sub(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('mul', () => { + it('1x2=2', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1)); + const y = Provable.witness(UInt32, () => new UInt32(2)); + x.mul(y).assertEquals(new UInt32(2)); + }); + }); + + it('1x0=0', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1)); + const y = Provable.witness(UInt32, () => new UInt32(0)); + x.mul(y).assertEquals(new UInt32(0)); + }); + }); + + it('1000x1000=1000000', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1000)); + const y = Provable.witness(UInt32, () => new UInt32(1000)); + x.mul(y).assertEquals(new UInt32(1000000)); + }); + }); + + it('MAXINTx1=MAXINT', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.MAXINT()); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.mul(y).assertEquals(UInt32.MAXINT()); + }); + }); + + it('should throw on overflow multiplication', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.MAXINT()); + const y = Provable.witness(UInt32, () => new UInt32(2)); + x.mul(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('div', () => { + it('2/1=2', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(2)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.div(y).assertEquals(new UInt32(2)); + }); + }); + + it('0/1=0', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(0)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.div(y).assertEquals(new UInt32(0)); + }); + }); + + it('2000/1000=2', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(2000)); + const y = Provable.witness(UInt32, () => new UInt32(1000)); + x.div(y).assertEquals(new UInt32(2)); + }); + }); + + it('MAXINT/1=MAXINT', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.MAXINT()); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.div(y).assertEquals(UInt32.MAXINT()); + }); + }); + + it('should throw on division by zero', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.MAXINT()); + const y = Provable.witness(UInt32, () => new UInt32(0)); + x.div(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('mod', () => { + it('1%1=0', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.mod(y).assertEquals(new UInt32(0)); + }); + }); + + it('500%32=20', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(500)); + const y = Provable.witness(UInt32, () => new UInt32(32)); + x.mod(y).assertEquals(new UInt32(20)); + }); + }); + + it('MAXINT%7=3', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.MAXINT()); + const y = Provable.witness(UInt32, () => new UInt32(7)); + x.mod(y).assertEquals(new UInt32(3)); + }); + }); + + it('should throw on mod by zero', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.MAXINT()); + const y = Provable.witness(UInt32, () => new UInt32(0)); + x.mod(y).assertEquals(new UInt32(1)); + }) + ).rejects.toThrow(); + }); + }); + + describe('assertLt', () => { + it('1<2=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1)); + const y = Provable.witness(UInt32, () => new UInt32(2)); + x.assertLessThan(y); + }); + }); + + it('1<1=false', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.assertLessThan(y); + }).catch(() => {}); + }); + + it('2<1=false', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(2)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.assertLessThan(y); + }).catch(() => {}); + }); + + it('1000<100000=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1000)); + const y = Provable.witness(UInt32, () => new UInt32(100000)); + x.assertLessThan(y); + }); + }); + + it('100000<1000=false', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(100000)); + const y = Provable.witness(UInt32, () => new UInt32(1000)); + x.assertLessThan(y); + }).catch(() => {}); + }); + + it('MAXINT { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.MAXINT()); + const y = Provable.witness(UInt32, () => UInt32.MAXINT()); + x.assertLessThan(y); + }).catch(() => {}); + }); + }); + + describe('assertLessThanOrEqual', () => { + it('1<=1=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.assertLessThanOrEqual(y); + }); + }); + + it('2<=1=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(2)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.assertLessThanOrEqual(y); + }) + ).rejects.toThrow(); + }); + + it('1000<=100000=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1000)); + const y = Provable.witness(UInt32, () => new UInt32(100000)); + x.assertLessThanOrEqual(y); + }); + }); + + it('100000<=1000=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(100000)); + const y = Provable.witness(UInt32, () => new UInt32(1000)); + x.assertLessThanOrEqual(y); + }) + ).rejects.toThrow(); + }); + + it('MAXINT<=MAXINT=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.MAXINT()); + const y = Provable.witness(UInt32, () => UInt32.MAXINT()); + x.assertLessThanOrEqual(y); + }); + }); + }); + + describe('assertGreaterThan', () => { + it('2>1=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(2)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.assertGreaterThan(y); + }); + }); + + it('1>1=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.assertGreaterThan(y); + }) + ).rejects.toThrow(); + }); + + it('1>2=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1)); + const y = Provable.witness(UInt32, () => new UInt32(2)); + x.assertGreaterThan(y); + }) + ).rejects.toThrow(); + }); + + it('100000>1000=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(100000)); + const y = Provable.witness(UInt32, () => new UInt32(1000)); + x.assertGreaterThan(y); + }); + }); + + it('1000>100000=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1000)); + const y = Provable.witness(UInt32, () => new UInt32(100000)); + x.assertGreaterThan(y); + }) + ).rejects.toThrow(); + }); + + it('MAXINT>MAXINT=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.MAXINT()); + const y = Provable.witness(UInt32, () => UInt32.MAXINT()); + x.assertGreaterThan(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('assertGreaterThanOrEqual', () => { + it('1<=1=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.assertGreaterThanOrEqual(y); + }); + }); + + it('1>=2=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1)); + const y = Provable.witness(UInt32, () => new UInt32(2)); + x.assertGreaterThanOrEqual(y); + }) + ).rejects.toThrow(); + }); + + it('100000>=1000=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(100000)); + const y = Provable.witness(UInt32, () => new UInt32(1000)); + x.assertGreaterThanOrEqual(y); + }); + }); + + it('1000>=100000=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => new UInt32(1000)); + const y = Provable.witness(UInt32, () => new UInt32(100000)); + x.assertGreaterThanOrEqual(y); + }) + ).rejects.toThrow(); + }); + + it('MAXINT>=MAXINT=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.MAXINT()); + const y = Provable.witness(UInt32, () => UInt32.MAXINT()); + x.assertGreaterThanOrEqual(y); + }); + }); + }); + + describe('from() ', () => { + describe('fromNumber()', () => { + it('should be the same as Field(1)', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.from(1)); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.assertEquals(y); + }); + }); + + it('should be the same as 2^53-1', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.from(NUMBERMAX)); + const y = Provable.witness( + UInt32, + () => new UInt32(String(NUMBERMAX)) + ); + x.assertEquals(y); + }); + }); + }); + describe('fromString()', () => { + it('should be the same as Field(1)', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => UInt32.from('1')); + const y = Provable.witness(UInt32, () => new UInt32(1)); + x.assertEquals(y); + }); + }); + + it('should be the same as 2^53-1', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt32, () => + UInt32.from(String(NUMBERMAX)) + ); + const y = Provable.witness( + UInt32, + () => new UInt32(String(NUMBERMAX)) + ); + x.assertEquals(y); + }); + }); + }); + }); + }); + + describe('Outside of circuit', () => { + describe('add', () => { + it('1+1=2', () => { + expect(new UInt32(1).add(1).toString()).toEqual('2'); + }); + + it('5000+5000=10000', () => { + expect(new UInt32(5000).add(5000).toString()).toEqual('10000'); + }); + + it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { + const value = ((1n << 32n) - 2n) / 2n; + expect( + new UInt32(value) + .add(new UInt32(value)) + .add(new UInt32(1)) + .toString() + ).toEqual(UInt32.MAXINT().toString()); + }); + + it('should throw on overflow addition', () => { + expect(() => { + UInt32.MAXINT().add(1); + }).toThrow(); + }); + }); + + describe('sub', () => { + it('1-1=0', () => { + expect(new UInt32(1).sub(1).toString()).toEqual('0'); + }); + + it('10000-5000=5000', () => { + expect(new UInt32(10000).sub(5000).toString()).toEqual('5000'); + }); + + it('should throw on sub if results in negative number', () => { + expect(() => { + UInt32.from(0).sub(1); + }).toThrow(); + }); + }); + + describe('mul', () => { + it('1x2=2', () => { + expect(new UInt32(1).mul(2).toString()).toEqual('2'); + }); + + it('1x0=0', () => { + expect(new UInt32(1).mul(0).toString()).toEqual('0'); + }); + + it('1000x1000=1000000', () => { + expect(new UInt32(1000).mul(1000).toString()).toEqual('1000000'); + }); + + it('MAXINTx1=MAXINT', () => { + expect(UInt32.MAXINT().mul(1).toString()).toEqual( + UInt32.MAXINT().toString() + ); + }); + + it('should throw on overflow multiplication', () => { + expect(() => { + UInt32.MAXINT().mul(2); + }).toThrow(); + }); + }); + + describe('div', () => { + it('2/1=2', () => { + expect(new UInt32(2).div(1).toString()).toEqual('2'); + }); + + it('0/1=0', () => { + expect(new UInt32(0).div(1).toString()).toEqual('0'); + }); + + it('2000/1000=2', () => { + expect(new UInt32(2000).div(1000).toString()).toEqual('2'); + }); + + it('MAXINT/1=MAXINT', () => { + expect(UInt32.MAXINT().div(1).toString()).toEqual( + UInt32.MAXINT().toString() + ); + }); + + it('should throw on division by zero', () => { + expect(() => { + UInt32.MAXINT().div(0); + }).toThrow(); + }); + }); + + describe('mod', () => { + it('1%1=0', () => { + expect(new UInt32(1).mod(1).toString()).toEqual('0'); + }); + + it('500%32=20', () => { + expect(new UInt32(500).mod(32).toString()).toEqual('20'); + }); + + it('MAXINT%7=3', () => { + expect(UInt32.MAXINT().mod(7).toString()).toEqual('3'); + }); + + it('should throw on mod by zero', () => { + expect(() => { + UInt32.MAXINT().mod(0); + }).toThrow(); + }); + }); + + describe('lessThan', () => { + it('1<2=true', () => { + expect(new UInt32(1).lessThan(new UInt32(2))).toEqual(Bool(true)); + }); + + it('1<1=false', () => { + expect(new UInt32(1).lessThan(new UInt32(1))).toEqual(Bool(false)); + }); + + it('2<1=false', () => { + expect(new UInt32(2).lessThan(new UInt32(1))).toEqual(Bool(false)); + }); + + it('1000<100000=true', () => { + expect(new UInt32(1000).lessThan(new UInt32(100000))).toEqual( + Bool(true) + ); + }); + + it('100000<1000=false', () => { + expect(new UInt32(100000).lessThan(new UInt32(1000))).toEqual( + Bool(false) + ); + }); + + it('MAXINT { + expect(UInt32.MAXINT().lessThan(UInt32.MAXINT())).toEqual( + Bool(false) + ); + }); + }); + + describe('lessThanOrEqual', () => { + it('1<=1=true', () => { + expect(new UInt32(1).lessThanOrEqual(new UInt32(1))).toEqual( + Bool(true) + ); + }); + + it('2<=1=false', () => { + expect(new UInt32(2).lessThanOrEqual(new UInt32(1))).toEqual( + Bool(false) + ); + }); + + it('1000<=100000=true', () => { + expect(new UInt32(1000).lessThanOrEqual(new UInt32(100000))).toEqual( + Bool(true) + ); + }); + + it('100000<=1000=false', () => { + expect(new UInt32(100000).lessThanOrEqual(new UInt32(1000))).toEqual( + Bool(false) + ); + }); + + it('MAXINT<=MAXINT=true', () => { + expect(UInt32.MAXINT().lessThanOrEqual(UInt32.MAXINT())).toEqual( + Bool(true) + ); + }); + }); + + describe('assertLessThanOrEqual', () => { + it('1<=1=true', () => { + expect(() => { + new UInt32(1).assertLessThanOrEqual(new UInt32(1)); + }).not.toThrow(); + }); + + it('2<=1=false', () => { + expect(() => { + new UInt32(2).assertLessThanOrEqual(new UInt32(1)); + }).toThrow(); + }); + + it('1000<=100000=true', () => { + expect(() => { + new UInt32(1000).assertLessThanOrEqual(new UInt32(100000)); + }).not.toThrow(); + }); + + it('100000<=1000=false', () => { + expect(() => { + new UInt32(100000).assertLessThanOrEqual(new UInt32(1000)); + }).toThrow(); + }); + + it('MAXINT<=MAXINT=true', () => { + expect(() => { + UInt32.MAXINT().assertLessThanOrEqual(UInt32.MAXINT()); + }).not.toThrow(); + }); + }); + + describe('greaterThan', () => { + it('2>1=true', () => { + expect(new UInt32(2).greaterThan(new UInt32(1))).toEqual(Bool(true)); + }); + + it('1>1=false', () => { + expect(new UInt32(1).greaterThan(new UInt32(1))).toEqual(Bool(false)); + }); + + it('1>2=false', () => { + expect(new UInt32(1).greaterThan(new UInt32(2))).toEqual(Bool(false)); + }); + + it('100000>1000=true', () => { + expect(new UInt32(100000).greaterThan(new UInt32(1000))).toEqual( + Bool(true) + ); + }); + + it('1000>100000=false', () => { + expect(new UInt32(1000).greaterThan(new UInt32(100000))).toEqual( + Bool(false) + ); + }); + + it('MAXINT>MAXINT=false', () => { + expect(UInt32.MAXINT().greaterThan(UInt32.MAXINT())).toEqual( + Bool(false) + ); + }); + }); + + describe('assertGreaterThan', () => { + it('1>1=false', () => { + expect(() => { + new UInt32(1).assertGreaterThan(new UInt32(1)); + }).toThrow(); + }); + + it('2>1=true', () => { + expect(() => { + new UInt32(2).assertGreaterThan(new UInt32(1)); + }).not.toThrow(); + }); + + it('1000>100000=false', () => { + expect(() => { + new UInt32(1000).assertGreaterThan(new UInt32(100000)); + }).toThrow(); + }); + + it('100000>1000=true', () => { + expect(() => { + new UInt32(100000).assertGreaterThan(new UInt32(1000)); + }).not.toThrow(); + }); + + it('MAXINT>MAXINT=false', () => { + expect(() => { + UInt32.MAXINT().assertGreaterThan(UInt32.MAXINT()); + }).toThrow(); + }); + }); + + describe('greaterThanOrEqual', () => { + it('2>=1=true', () => { + expect(new UInt32(2).greaterThanOrEqual(new UInt32(1))).toEqual( + Bool(true) + ); + }); + + it('1>=1=true', () => { + expect(new UInt32(1).greaterThanOrEqual(new UInt32(1))).toEqual( + Bool(true) + ); + }); + + it('1>=2=false', () => { + expect(new UInt32(1).greaterThanOrEqual(new UInt32(2))).toEqual( + Bool(false) + ); + }); + + it('100000>=1000=true', () => { + expect( + new UInt32(100000).greaterThanOrEqual(new UInt32(1000)) + ).toEqual(Bool(true)); + }); + + it('1000>=100000=false', () => { + expect( + new UInt32(1000).greaterThanOrEqual(new UInt32(100000)) + ).toEqual(Bool(false)); + }); + + it('MAXINT>=MAXINT=true', () => { + expect(UInt32.MAXINT().greaterThanOrEqual(UInt32.MAXINT())).toEqual( + Bool(true) + ); + }); + }); + + describe('assertGreaterThanOrEqual', () => { + it('1>=1=true', () => { + expect(() => { + new UInt32(1).assertGreaterThanOrEqual(new UInt32(1)); + }).not.toThrow(); + }); + + it('2>=1=true', () => { + expect(() => { + new UInt32(2).assertGreaterThanOrEqual(new UInt32(1)); + }).not.toThrow(); + }); + + it('1000>=100000=false', () => { + expect(() => { + new UInt32(1000).assertGreaterThanOrEqual(new UInt32(100000)); + }).toThrow(); + }); + + it('100000>=1000=true', () => { + expect(() => { + new UInt32(100000).assertGreaterThanOrEqual(new UInt32(1000)); + }).not.toThrow(); + }); + + it('MAXINT>=MAXINT=true', () => { + expect(() => { + UInt32.MAXINT().assertGreaterThanOrEqual(UInt32.MAXINT()); + }).not.toThrow(); + }); + }); + + describe('toString()', () => { + it('should be the same as Field(0)', async () => { + const x = new UInt32(0); + const y = Field(0); + expect(x.toString()).toEqual(y.toString()); + }); + it('should be the same as 2^32-1', async () => { + const x = new UInt32(String(NUMBERMAX)); + const y = Field(String(NUMBERMAX)); + expect(x.toString()).toEqual(y.toString()); + }); + }); + + describe('check()', () => { + it('should pass checking a MAXINT', () => { + expect(() => { + UInt32.check(UInt32.MAXINT()); + }).not.toThrow(); + }); + + it('should throw checking over MAXINT', () => { + const aboveMax = new UInt32(1); + aboveMax.value = Field(1n << 32n); + expect(() => { + UInt32.check(aboveMax); + }).toThrow(); + }); + }); + + describe('from() ', () => { + describe('fromNumber()', () => { + it('should be the same as Field(1)', () => { + const x = UInt32.from(1); + expect(x.value).toEqual(new UInt32(1).value); + }); + + it('should be the same as 2^53-1', () => { + const x = UInt32.from(NUMBERMAX); + expect(x.value).toEqual(Field(String(NUMBERMAX))); + }); + }); + describe('fromString()', () => { + it('should be the same as Field(1)', () => { + const x = UInt32.from('1'); + expect(x.value).toEqual(new UInt32(1).value); + }); + + it('should be the same as 2^53-1', () => { + const x = UInt32.from(String(NUMBERMAX)); + expect(x.value).toEqual(Field(String(NUMBERMAX))); + }); + }); + }); + }); + }); + + describe('UInt8', () => { + const NUMBERMAX = UInt8.MAXINT().value; + + describe('Inside circuit', () => { + describe('add', () => { + it('1+1=2', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.add(y).assertEquals(2); + }); + }); + + it('100+100=200', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(100)); + x.add(y).assertEquals(new UInt8(200)); + }); + }); + + it('(MAXINT/2+MAXINT/2) adds to MAXINT', async () => { + const n = ((1n << 8n) - 2n) / 2n; + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(n)); + const y = Provable.witness(UInt8, () => new UInt8(n)); + x.add(y).add(1).assertEquals(UInt8.MAXINT()); + }); + }); + + it('should throw on overflow addition', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.add(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('sub', () => { + it('1-1=0', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.sub(y).assertEquals(new UInt8(0)); + }); + }); + + it('100-50=50', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(50)); + x.sub(y).assertEquals(new UInt8(50)); + }); + }); + + it('should throw on sub if results in negative number', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(0)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.sub(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('mul', () => { + it('1x2=2', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(2)); + x.mul(y).assertEquals(new UInt8(2)); + }); + }); + + it('1x0=0', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(0)); + x.mul(y).assertEquals(new UInt8(0)); + }); + }); + + it('12x20=240', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(12)); + const y = Provable.witness(UInt8, () => new UInt8(20)); + x.mul(y).assertEquals(new UInt8(240)); + }); + }); + + it('MAXINTx1=MAXINT', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.mul(y).assertEquals(UInt8.MAXINT()); + }); + }); + + it('should throw on overflow multiplication', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(2)); + x.mul(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('div', () => { + it('2/1=2', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(2)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.div(y).assertEquals(new UInt8(2)); + }); + }); + + it('0/1=0', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(0)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.div(y).assertEquals(new UInt8(0)); + }); + }); + + it('20/10=2', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(20)); + const y = Provable.witness(UInt8, () => new UInt8(10)); + x.div(y).assertEquals(new UInt8(2)); + }); + }); + + it('MAXINT/1=MAXINT', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.div(y).assertEquals(UInt8.MAXINT()); + }); + }); + + it('should throw on division by zero', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(0)); + x.div(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('mod', () => { + it('1%1=0', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.mod(y).assertEquals(new UInt8(0)); + }); + }); + + it('50%32=18', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(50)); + const y = Provable.witness(UInt8, () => new UInt8(32)); + x.mod(y).assertEquals(new UInt8(18)); + }); + }); + + it('MAXINT%7=3', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(7)); + x.mod(y).assertEquals(new UInt8(3)); + }); + }); + + it('should throw on mod by zero', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(0)); + x.mod(y).assertEquals(new UInt8(1)); + }) + ).rejects.toThrow(); + }); + }); + + describe('assertLt', () => { + it('1<2=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(2)); + x.assertLessThan(y); + }); + }); + + it('1<1=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertLessThan(y); + }) + ).rejects.toThrow(); + }); + + it('2<1=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(2)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertLessThan(y); + }) + ).rejects.toThrow(); + }); + + it('10<100=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(10)); + const y = Provable.witness(UInt8, () => new UInt8(100)); + x.assertLessThan(y); + }); + }); + + it('100<10=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(10)); + x.assertLessThan(y); + }) + ).rejects.toThrow(); + }); + + it('MAXINT { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => UInt8.MAXINT()); + x.assertLessThan(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('assertLessThanOrEqual', () => { + it('1<=1=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertLessThanOrEqual(y); + }); + }); + + it('2<=1=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(2)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertLessThanOrEqual(y); + }) + ).rejects.toThrow(); + }); + + it('10<=100=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(10)); + const y = Provable.witness(UInt8, () => new UInt8(100)); + x.assertLessThanOrEqual(y); + }); + }); + + it('100<=10=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(10)); + x.assertLessThanOrEqual(y); + }) + ).rejects.toThrow(); + }); + + it('MAXINT<=MAXINT=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => UInt8.MAXINT()); + x.assertLessThanOrEqual(y); + }); + }); + }); + + describe('assertGreaterThan', () => { + it('2>1=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(2)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertGreaterThan(y); + }); + }); + + it('1>1=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertGreaterThan(y); + }) + ).rejects.toThrow(); + }); + + it('1>2=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(2)); + x.assertGreaterThan(y); + }) + ).rejects.toThrow(); + }); + + it('100>10=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(10)); + x.assertGreaterThan(y); + }); + }); + + it('10>100=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(1000)); + const y = Provable.witness(UInt8, () => new UInt8(100000)); + x.assertGreaterThan(y); + }) + ).rejects.toThrow(); + }); + + it('MAXINT>MAXINT=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => UInt8.MAXINT()); + x.assertGreaterThan(y); + }) + ).rejects.toThrow(); + }); + }); + + describe('assertGreaterThanOrEqual', () => { + it('1<=1=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertGreaterThanOrEqual(y); + }); + }); + + it('1>=2=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(2)); + x.assertGreaterThanOrEqual(y); + }) + ).rejects.toThrow(); + }); + + it('100>=10=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(10)); + x.assertGreaterThanOrEqual(y); + }); + }); + + it('10>=100=false', async () => { + await expect( + Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => new UInt8(10)); + const y = Provable.witness(UInt8, () => new UInt8(100)); + x.assertGreaterThanOrEqual(y); + }) + ).rejects.toThrow(); + }); + + it('MAXINT>=MAXINT=true', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => UInt8.MAXINT()); + x.assertGreaterThanOrEqual(y); + }); + }); + }); + + describe('from() ', () => { + describe('fromNumber()', () => { + it('should be the same as Field(1)', async () => { + await Provable.runAndCheck(async () => { + const x = Provable.witness(UInt8, () => UInt8.from(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertEquals(y); + }); + }); + }); + }); + }); + + describe('Outside of circuit', () => { + describe('add', () => { + it('1+1=2', () => { + expect(new UInt8(1).add(1).toString()).toEqual('2'); + }); + + it('50+50=100', () => { + expect(new UInt8(50).add(50).toString()).toEqual('100'); + }); + + it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { + const value = ((1n << 8n) - 2n) / 2n; + expect( + new UInt8(value).add(new UInt8(value)).add(new UInt8(1)).toString() + ).toEqual(UInt8.MAXINT().toString()); + }); + + it('should throw on overflow addition', () => { + expect(() => { + UInt8.MAXINT().add(1); + }).toThrow(); + }); + }); + + describe('sub', () => { + it('1-1=0', () => { + expect(new UInt8(1).sub(1).toString()).toEqual('0'); + }); + + it('100-50=50', () => { + expect(new UInt8(100).sub(50).toString()).toEqual('50'); + }); + + it('should throw on sub if results in negative number', () => { + expect(() => { + UInt8.from(0).sub(1); + }).toThrow(); + }); + }); + + describe('mul', () => { + it('1x2=2', () => { + expect(new UInt8(1).mul(2).toString()).toEqual('2'); + }); + + it('1x0=0', () => { + expect(new UInt8(1).mul(0).toString()).toEqual('0'); + }); + + it('12x20=240', () => { + expect(new UInt8(12).mul(20).toString()).toEqual('240'); + }); + + it('MAXINTx1=MAXINT', () => { + expect(UInt8.MAXINT().mul(1).toString()).toEqual( + UInt8.MAXINT().toString() + ); + }); + + it('should throw on overflow multiplication', () => { + expect(() => { + UInt8.MAXINT().mul(2); + }).toThrow(); + }); + }); + + describe('div', () => { + it('2/1=2', () => { + expect(new UInt8(2).div(1).toString()).toEqual('2'); + }); + + it('0/1=0', () => { + expect(new UInt8(0).div(1).toString()).toEqual('0'); + }); + + it('20/10=2', () => { + expect(new UInt8(20).div(10).toString()).toEqual('2'); + }); + + it('MAXINT/1=MAXINT', () => { + expect(UInt8.MAXINT().div(1).toString()).toEqual( + UInt8.MAXINT().toString() + ); + }); + + it('should throw on division by zero', () => { + expect(() => { + UInt8.MAXINT().div(0); + }).toThrow(); + }); + }); + + describe('mod', () => { + it('1%1=0', () => { + expect(new UInt8(1).mod(1).toString()).toEqual('0'); + }); + + it('50%32=18', () => { + expect(new UInt8(50).mod(32).toString()).toEqual('18'); + }); + + it('MAXINT%7=3', () => { + expect(UInt8.MAXINT().mod(7).toString()).toEqual('3'); + }); + + it('should throw on mod by zero', () => { + expect(() => { + UInt8.MAXINT().mod(0); + }).toThrow(); + }); + }); + + describe('lessThan', () => { + it('1<2=true', () => { + expect(new UInt8(1).lessThan(new UInt8(2))).toEqual(Bool(true)); + }); + + it('1<1=false', () => { + expect(new UInt8(1).lessThan(new UInt8(1))).toEqual(Bool(false)); + }); + + it('2<1=false', () => { + expect(new UInt8(2).lessThan(new UInt8(1))).toEqual(Bool(false)); + }); + + it('10<100=true', () => { + expect(new UInt8(10).lessThan(new UInt8(100))).toEqual(Bool(true)); + }); + + it('100<10=false', () => { + expect(new UInt8(100).lessThan(new UInt8(10))).toEqual(Bool(false)); + }); + + it('MAXINT { + expect(UInt8.MAXINT().lessThan(UInt8.MAXINT())).toEqual(Bool(false)); + }); + }); + + describe('lessThanOrEqual', () => { + it('1<=1=true', () => { + expect(new UInt8(1).lessThanOrEqual(new UInt8(1))).toEqual( + Bool(true) + ); + }); + + it('2<=1=false', () => { + expect(new UInt8(2).lessThanOrEqual(new UInt8(1))).toEqual( + Bool(false) + ); + }); + + it('10<=100=true', () => { + expect(new UInt8(10).lessThanOrEqual(new UInt8(100))).toEqual( + Bool(true) + ); + }); + + it('100<=10=false', () => { + expect(new UInt8(100).lessThanOrEqual(new UInt8(10))).toEqual( + Bool(false) + ); + }); + + it('MAXINT<=MAXINT=true', () => { + expect(UInt8.MAXINT().lessThanOrEqual(UInt8.MAXINT())).toEqual( + Bool(true) + ); + }); + }); + + describe('assertLessThanOrEqual', () => { + it('1<=1=true', () => { + expect(() => { + new UInt8(1).assertLessThanOrEqual(new UInt8(1)); + }).not.toThrow(); + }); + + it('2<=1=false', () => { + expect(() => { + new UInt8(2).assertLessThanOrEqual(new UInt8(1)); + }).toThrow(); + }); + + it('10<=100=true', () => { + expect(() => { + new UInt8(10).assertLessThanOrEqual(new UInt8(100)); + }).not.toThrow(); + }); + + it('100<=10=false', () => { + expect(() => { + new UInt8(100).assertLessThanOrEqual(new UInt8(10)); + }).toThrow(); + }); + + it('MAXINT<=MAXINT=true', () => { + expect(() => { + UInt8.MAXINT().assertLessThanOrEqual(UInt8.MAXINT()); + }).not.toThrow(); + }); + }); + + describe('greaterThan', () => { + it('2>1=true', () => { + expect(new UInt8(2).greaterThan(new UInt8(1))).toEqual(Bool(true)); + }); + + it('1>1=false', () => { + expect(new UInt8(1).greaterThan(new UInt8(1))).toEqual(Bool(false)); + }); + + it('1>2=false', () => { + expect(new UInt8(1).greaterThan(new UInt8(2))).toEqual(Bool(false)); + }); + + it('100>10=true', () => { + expect(new UInt8(100).greaterThan(new UInt8(10))).toEqual(Bool(true)); + }); + + it('10>100=false', () => { + expect(new UInt8(10).greaterThan(new UInt8(100))).toEqual( + Bool(false) + ); + }); + + it('MAXINT>MAXINT=false', () => { + expect(UInt8.MAXINT().greaterThan(UInt8.MAXINT())).toEqual( + Bool(false) + ); + }); + }); + + describe('assertGreaterThan', () => { + it('1>1=false', () => { + expect(() => { + new UInt8(1).assertGreaterThan(new UInt8(1)); + }).toThrow(); + }); + + it('2>1=true', () => { + expect(() => { + new UInt8(2).assertGreaterThan(new UInt8(1)); + }).not.toThrow(); + }); + + it('10>100=false', () => { + expect(() => { + new UInt8(10).assertGreaterThan(new UInt8(100)); + }).toThrow(); + }); + + it('100000>1000=true', () => { + expect(() => { + new UInt8(100).assertGreaterThan(new UInt8(10)); + }).not.toThrow(); + }); + + it('MAXINT>MAXINT=false', () => { + expect(() => { + UInt8.MAXINT().assertGreaterThan(UInt8.MAXINT()); + }).toThrow(); + }); + }); + + describe('greaterThanOrEqual', () => { + it('2>=1=true', () => { + expect(new UInt8(2).greaterThanOrEqual(new UInt8(1))).toEqual( + Bool(true) + ); + }); + + it('1>=1=true', () => { + expect(new UInt8(1).greaterThanOrEqual(new UInt8(1))).toEqual( + Bool(true) + ); + }); + + it('1>=2=false', () => { + expect(new UInt8(1).greaterThanOrEqual(new UInt8(2))).toEqual( + Bool(false) + ); + }); + + it('100>=10=true', () => { + expect(new UInt8(100).greaterThanOrEqual(new UInt8(10))).toEqual( + Bool(true) + ); + }); + + it('10>=100=false', () => { + expect(new UInt8(10).greaterThanOrEqual(new UInt8(100))).toEqual( + Bool(false) + ); + }); + + it('MAXINT>=MAXINT=true', () => { + expect(UInt8.MAXINT().greaterThanOrEqual(UInt8.MAXINT())).toEqual( + Bool(true) + ); + }); + }); + + describe('assertGreaterThanOrEqual', () => { + it('1>=1=true', () => { + expect(() => { + new UInt8(1).assertGreaterThanOrEqual(new UInt8(1)); + }).not.toThrow(); + }); + + it('2>=1=true', () => { + expect(() => { + new UInt8(2).assertGreaterThanOrEqual(new UInt8(1)); + }).not.toThrow(); + }); + + it('10>=100=false', () => { + expect(() => { + new UInt8(10).assertGreaterThanOrEqual(new UInt8(100)); + }).toThrow(); + }); + + it('100>=10=true', () => { + expect(() => { + new UInt8(100).assertGreaterThanOrEqual(new UInt8(10)); + }).not.toThrow(); + }); + + it('MAXINT>=MAXINT=true', () => { + expect(() => { + UInt32.MAXINT().assertGreaterThanOrEqual(UInt32.MAXINT()); + }).not.toThrow(); + }); + }); + + describe('toString()', () => { + it('should be the same as Field(0)', async () => { + const x = new UInt8(0); + const y = Field(0); + expect(x.toString()).toEqual(y.toString()); + }); + it('should be the same as 2^8-1', async () => { + const x = new UInt8(NUMBERMAX.toBigInt()); + const y = Field(String(NUMBERMAX)); + expect(x.toString()).toEqual(y.toString()); + }); + }); + + describe('check()', () => { + it('should pass checking a MAXINT', () => { + expect(() => { + UInt8.check(UInt8.MAXINT()); + }).not.toThrow(); + }); + + it('should throw checking over MAXINT', () => { + const x = UInt8.MAXINT(); + expect(() => { + UInt8.check(x.add(1)); + }).toThrow(); + }); + }); + + describe('from() ', () => { + describe('fromNumber()', () => { + it('should be the same as Field(1)', () => { + const x = UInt8.from(1); + expect(x.value).toEqual(Field(1)); + }); + + it('should be the same as 2^53-1', () => { + const x = UInt8.from(NUMBERMAX); + expect(x.value).toEqual(NUMBERMAX); + }); + }); + }); + }); + }); +}); diff --git a/src/lib/provable/test/keccak.unit-test.ts b/src/lib/provable/test/keccak.unit-test.ts new file mode 100644 index 0000000000..1678b0a231 --- /dev/null +++ b/src/lib/provable/test/keccak.unit-test.ts @@ -0,0 +1,271 @@ +import { Keccak } from '../crypto/keccak.js'; +import { ZkProgram } from '../../proof-system/zkprogram.js'; +import { + equivalentProvable, + equivalent, + equivalentAsync, +} from '../../testing/equivalent.js'; +import { + keccak_224, + keccak_256, + keccak_384, + keccak_512, + sha3_224, + sha3_256, + sha3_384, + sha3_512, +} from '@noble/hashes/sha3'; +import { Bytes } from '../wrapped-classes.js'; +import { bytes } from './test-utils.js'; +import { UInt8 } from '../int.js'; +import { test, Random, sample } from '../../testing/property.js'; +import { expect } from 'expect'; + +const RUNS = 1; + +const testImplementations = { + sha3: { + 224: sha3_224, + 256: sha3_256, + 384: sha3_384, + 512: sha3_512, + }, + preNist: { + 224: keccak_224, + 256: keccak_256, + 384: keccak_384, + 512: keccak_512, + }, +}; + +const lengths = [256, 384, 512] as const; + +// EQUIVALENCE TESTS AGAINST REF IMPLEMENTATION + +// checks outside circuit +// TODO: fix witness generation slowness + +for (let length of lengths) { + let [preimageLength] = sample(Random.nat(100), 1); + console.log(`Testing ${length} with preimage length ${preimageLength}`); + let inputBytes = bytes(preimageLength); + let outputBytes = bytes(length / 8); + + equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( + testImplementations.sha3[length], + (x) => Keccak.nistSha3(length, x), + `sha3 ${length}` + ); + + equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( + testImplementations.preNist[length], + (x) => Keccak.preNist(length, x), + `keccak ${length}` + ); + + // bytes to hex roundtrip + equivalent({ from: [inputBytes], to: inputBytes })( + (x) => x, + (x) => Bytes.fromHex(x.toHex()), + `Bytes toHex` + ); +} + +// EQUIVALENCE TESTS AGAINST TEST VECTORS (at the bottom) + +for (let { nist, length, message, expected } of testVectors()) { + let Hash = nist ? Keccak.nistSha3 : Keccak.preNist; + let actual = Hash(length, Bytes.fromHex(message)); + expect(actual).toEqual(Bytes.fromHex(expected)); +} + +// MISC QUICK TESTS + +// Test constructor +test(Random.uint8, Random.uint8, (x, y, assert) => { + let z = new UInt8(x); + assert(z instanceof UInt8); + assert(z.toBigInt() === x); + assert(z.toString() === x.toString()); + + assert((z = new UInt8(x)) instanceof UInt8 && z.toBigInt() === x); + assert((z = new UInt8(z)) instanceof UInt8 && z.toBigInt() === x); + assert((z = new UInt8(z.value.value)) instanceof UInt8 && z.toBigInt() === x); + + z = new UInt8(y); + assert(z instanceof UInt8); + assert(z.toString() === y.toString()); +}); + +// handles all numbers up to 2^8 +test(Random.nat(255), (n, assert) => { + assert(UInt8.from(n).toString() === String(n)); +}); + +// throws on negative numbers +test.negative(Random.int(-10, -1), (x) => UInt8.from(x)); + +// throws on numbers >= 2^8 +test.negative(Random.uint8.invalid, (x) => UInt8.from(x)); + +// PROOF TESTS + +// Choose a test length at random +const digestLength = lengths[Math.floor(Math.random() * 3)]; + +// Digest length in bytes +const digestLengthBytes = digestLength / 8; + +const preImageLength = 32; + +// No need to test Ethereum because it's just a special case of preNist +const KeccakProgram = ZkProgram({ + name: `keccak-test-${digestLength}`, + publicInput: Bytes(preImageLength).provable, + publicOutput: Bytes(digestLengthBytes).provable, + methods: { + nistSha3: { + privateInputs: [], + async method(preImage: Bytes) { + return Keccak.nistSha3(digestLength, preImage); + }, + }, + preNist: { + privateInputs: [], + async method(preImage: Bytes) { + return Keccak.preNist(digestLength, preImage); + }, + }, + }, +}); + +await KeccakProgram.compile(); + +// SHA-3 +await equivalentAsync( + { + from: [bytes(preImageLength)], + to: bytes(digestLengthBytes), + }, + { runs: RUNS } +)(testImplementations.sha3[digestLength], async (x) => { + const proof = await KeccakProgram.nistSha3(x); + await KeccakProgram.verify(proof); + return proof.publicOutput; +}); + +// PreNIST Keccak +await equivalentAsync( + { + from: [bytes(preImageLength)], + to: bytes(digestLengthBytes), + }, + { runs: RUNS } +)(testImplementations.preNist[digestLength], async (x) => { + const proof = await KeccakProgram.preNist(x); + await KeccakProgram.verify(proof); + return proof.publicOutput; +}); + +// TEST VECTORS + +function testVectors(): { + nist: boolean; + length: 256 | 384 | 512; + message: string; + expected: string; +}[] { + return [ + { + nist: false, + length: 256, + message: '30', + expected: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d', + }, + { + nist: true, + length: 512, + message: '30', + expected: + '2d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c', + }, + { + nist: false, + length: 256, + message: + '4920616d20746865206f776e6572206f6620746865204e465420776974682069642058206f6e2074686520457468657265756d20636861696e', + expected: + '63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + }, + { + nist: false, + length: 256, + message: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116df9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e42d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + expected: + '560deb1d387f72dba729f0bd0231ad45998dda4b53951645322cf95c7b6261d9', + }, + { + nist: true, + length: 256, + message: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116df9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e42d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + expected: + '1784354c4bbfa5f54e5db23041089e65a807a7b970e3cfdba95e2fbe63b1c0e4', + }, + { + nist: false, + length: 256, + message: + '391ccf9b5de23bb86ec6b2b142adb6e9ba6bee8519e7502fb8be8959fbd2672934cc3e13b7b45bf2b8a5cb48881790a7438b4a326a0c762e31280711e6b64fcc2e3e4e631e501d398861172ea98603618b8f23b91d0208b0b992dfe7fdb298b6465adafbd45e4f88ee9dc94e06bc4232be91587f78572c169d4de4d8b95b714ea62f1fbf3c67a4', + expected: + '7d5655391ede9ca2945f32ad9696f464be8004389151ce444c89f688278f2e1d', + }, + { + nist: false, + length: 256, + message: + 'ff391ccf9b5de23bb86ec6b2b142adb6e9ba6bee8519e7502fb8be8959fbd2672934cc3e13b7b45bf2b8a5cb48881790a7438b4a326a0c762e31280711e6b64fcc2e3e4e631e501d398861172ea98603618b8f23b91d0208b0b992dfe7fdb298b6465adafbd45e4f88ee9dc94e06bc4232be91587f78572c169d4de4d8b95b714ea62f1fbf3c67a4', + expected: + '37694fd4ba137be747eb25a85b259af5563e0a7a3010d42bd15963ac631b9d3f', + }, + { + nist: false, + length: 256, + message: + '80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', + expected: + 'bbf1f49a2cc5678aa62196d0c3108d89425b81780e1e90bcec03b4fb5f834714', + }, + { + nist: false, + length: 256, + message: + '80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', + expected: + 'bbf1f49a2cc5678aa62196d0c3108d89425b81780e1e90bcec03b4fb5f834714', + }, + { + nist: false, + length: 256, + message: 'a2c0', + expected: + '9856642c690c036527b8274db1b6f58c0429a88d9f3b9298597645991f4f58f0', + }, + { + nist: false, + length: 256, + message: '0a2c', + expected: + '295b48ad49eff61c3abfd399c672232434d89a4ef3ca763b9dbebb60dbb32a8b', + }, + { + nist: false, + length: 256, + message: '00', + expected: + 'bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a', + }, + ]; +} diff --git a/src/lib/merkle_map.test.ts b/src/lib/provable/test/merkle-map.test.ts similarity index 83% rename from src/lib/merkle_map.test.ts rename to src/lib/provable/test/merkle-map.test.ts index 40ba330ee8..3623328ca4 100644 --- a/src/lib/merkle_map.test.ts +++ b/src/lib/provable/test/merkle-map.test.ts @@ -1,13 +1,6 @@ -import { isReady, shutdown, Field, MerkleMap } from 'snarkyjs'; +import { Field, MerkleMap } from 'o1js'; describe('Merkle Map', () => { - beforeAll(async () => { - await isReady; - }); - afterAll(async () => { - setTimeout(shutdown, 0); - }); - it('set and get a value from a key', () => { const map = new MerkleMap(); diff --git a/src/lib/merkle_tree.test.ts b/src/lib/provable/test/merkle-tree.test.ts similarity index 91% rename from src/lib/merkle_tree.test.ts rename to src/lib/provable/test/merkle-tree.test.ts index 96ffc686c8..900a7fda70 100644 --- a/src/lib/merkle_tree.test.ts +++ b/src/lib/provable/test/merkle-tree.test.ts @@ -1,20 +1,6 @@ -import { - isReady, - shutdown, - Poseidon, - Field, - MerkleTree, - MerkleWitness, -} from 'snarkyjs'; +import { Poseidon, Field, MerkleTree, MerkleWitness } from 'o1js'; describe('Merkle Tree', () => { - beforeAll(async () => { - await isReady; - }); - afterAll(async () => { - setTimeout(shutdown, 0); - }); - it('root of empty tree of size 1', () => { const tree = new MerkleTree(1); expect(tree.getRoot().toString()).toEqual(Field(0).toString()); diff --git a/src/lib/merkle-tree.unit-test.ts b/src/lib/provable/test/merkle-tree.unit-test.ts similarity index 56% rename from src/lib/merkle-tree.unit-test.ts rename to src/lib/provable/test/merkle-tree.unit-test.ts index fe81ef1756..7eb7e7415d 100644 --- a/src/lib/merkle-tree.unit-test.ts +++ b/src/lib/provable/test/merkle-tree.unit-test.ts @@ -1,15 +1,10 @@ -import { Bool, Field } from './core.js'; -import { maybeSwap, maybeSwapBad } from './merkle_tree.js'; -import { Random, test } from './testing/property.js'; +import { Bool, Field } from '../wrapped.js'; +import { maybeSwap } from '../merkle-tree.js'; +import { Random, test } from '../../testing/property.js'; import { expect } from 'expect'; test(Random.bool, Random.field, Random.field, (b, x, y) => { let [x0, y0] = maybeSwap(Bool(!!b), Field(x), Field(y)); - let [x1, y1] = maybeSwapBad(Bool(!!b), Field(x), Field(y)); - - // both versions of `maybeSwap` should behave the same - expect(x0).toEqual(x1); - expect(y0).toEqual(y1); // if the boolean is true, it shouldn't swap the fields; otherwise, it should if (b) { diff --git a/src/lib/nullifier.unit-test.ts b/src/lib/provable/test/nullifier.unit-test.ts similarity index 87% rename from src/lib/nullifier.unit-test.ts rename to src/lib/provable/test/nullifier.unit-test.ts index 50af523e26..fda3a33836 100644 --- a/src/lib/nullifier.unit-test.ts +++ b/src/lib/provable/test/nullifier.unit-test.ts @@ -1,7 +1,7 @@ -import { createNullifier } from '../mina-signer/src/nullifier.js'; -import { Field } from './core.js'; -import { Nullifier } from './nullifier.js'; -import { PrivateKey } from './signature.js'; +import { createNullifier } from '../../../mina-signer/src/nullifier.js'; +import { Field } from '../wrapped.js'; +import { Nullifier } from '../crypto/nullifier.js'; +import { PrivateKey } from '../crypto/signature.js'; let priv = PrivateKey.random(); diff --git a/src/lib/primitives.test.ts b/src/lib/provable/test/primitives.test.ts similarity index 64% rename from src/lib/primitives.test.ts rename to src/lib/provable/test/primitives.test.ts index 65bb7b8bbe..a8ffcebf95 100644 --- a/src/lib/primitives.test.ts +++ b/src/lib/provable/test/primitives.test.ts @@ -1,266 +1,226 @@ -import { isReady, shutdown, Field, Bool, Provable } from 'snarkyjs'; +import { Field, Bool, Provable } from 'o1js'; describe('bool', () => { - beforeAll(async () => { - await isReady; - return; - }); - - afterAll(async () => { - setTimeout(async () => { - await shutdown(); - }, 0); - }); - describe('inside circuit', () => { describe('toField', () => { - it('should return a Field', async () => { - expect(true).toEqual(true); - }); - it('should convert false to Field element 0', () => { - expect(() => { - Provable.runAndCheck(() => { - const xFalse = Provable.witness(Bool, () => new Bool(false)); + it('should convert false to Field element 0', async () => { + await Provable.runAndCheck(() => { + const xFalse = Provable.witness(Bool, () => new Bool(false)); - xFalse.toField().assertEquals(new Field(0)); - }); - }).not.toThrow(); + xFalse.toField().assertEquals(new Field(0)); + }); }); - it('should throw when false toString is compared to Field element other than 0 ', () => { - expect(() => { + it('should throw when false toString is compared to Field element other than 0 ', async () => { + await expect( Provable.runAndCheck(() => { const xFalse = Provable.witness(Bool, () => new Bool(false)); xFalse.toField().assertEquals(new Field(1)); - }); - }).toThrow(); + }) + ).rejects.toThrow(); }); - it('should convert true to Field element 1', () => { - expect(() => { - Provable.runAndCheck(() => { - const xTrue = Provable.witness(Bool, () => new Bool(true)); - xTrue.toField().assertEquals(new Field(1)); - }); - }).not.toThrow(); + it('should convert true to Field element 1', async () => { + await Provable.runAndCheck(() => { + const xTrue = Provable.witness(Bool, () => new Bool(true)); + xTrue.toField().assertEquals(new Field(1)); + }); }); - it('should throw when true toField is compared to Field element other than 1 ', () => { - expect(() => { + it('should throw when true toField is compared to Field element other than 1 ', async () => { + await expect( Provable.runAndCheck(() => { const xTrue = Provable.witness(Bool, () => new Bool(true)); xTrue.toField().assertEquals(new Field(0)); - }); - }).toThrow(); + }) + ).rejects.toThrow(); }); }); describe('toFields', () => { - it('should return an array of Fields', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Bool, () => new Bool(false)); - const fieldArr = x.toFields(); - const isArr = Array.isArray(fieldArr); - expect(isArr).toBe(true); - fieldArr[0].assertEquals(new Field(0)); - }); - }).not.toThrow(); + it('should return an array of Fields', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Bool, () => new Bool(false)); + const fieldArr = x.toFields(); + const isArr = Array.isArray(fieldArr); + expect(isArr).toBe(true); + fieldArr[0].assertEquals(new Field(0)); + }); }); }); describe('and', () => { it('true "and" true should return true', async () => { - expect(() => { - Provable.runAndCheck(() => { - const xTrue = Provable.witness(Bool, () => new Bool(true)); - const yTrue = Provable.witness(Bool, () => new Bool(true)); + await Provable.runAndCheck(() => { + const xTrue = Provable.witness(Bool, () => new Bool(true)); + const yTrue = Provable.witness(Bool, () => new Bool(true)); - xTrue.and(yTrue).assertEquals(new Bool(true)); - }); - }).not.toThrow(); + xTrue.and(yTrue).assertEquals(new Bool(true)); + }); }); it('should throw if true "and" true is compared to false', async () => { - expect(() => { + await expect( Provable.runAndCheck(() => { const xTrue = Provable.witness(Bool, () => new Bool(true)); const yTrue = Provable.witness(Bool, () => new Bool(true)); xTrue.and(yTrue).assertEquals(new Bool(false)); - }); - }).toThrow(); + }) + ).rejects.toThrow(); }); it('false "and" false should return false', async () => { - expect(() => { - Provable.runAndCheck(() => { - const xFalse = Provable.witness(Bool, () => new Bool(false)); - const yFalse = Provable.witness(Bool, () => new Bool(false)); + await Provable.runAndCheck(() => { + const xFalse = Provable.witness(Bool, () => new Bool(false)); + const yFalse = Provable.witness(Bool, () => new Bool(false)); - xFalse.and(yFalse).assertEquals(new Bool(false)); - }); - }).not.toThrow(); + xFalse.and(yFalse).assertEquals(new Bool(false)); + }); }); it('should throw if false "and" false is compared to true', async () => { - expect(() => { + await expect( Provable.runAndCheck(() => { const xFalse = Provable.witness(Bool, () => new Bool(false)); const yFalse = Provable.witness(Bool, () => new Bool(false)); xFalse.and(yFalse).assertEquals(new Bool(true)); - }); - }).toThrow(); + }) + ).rejects.toThrow(); }); it('false "and" true should return false', async () => { - expect(() => { - Provable.runAndCheck(() => { - const xFalse = Provable.witness(Bool, () => new Bool(false)); - const yTrue = Provable.witness(Bool, () => new Bool(true)); + await Provable.runAndCheck(() => { + const xFalse = Provable.witness(Bool, () => new Bool(false)); + const yTrue = Provable.witness(Bool, () => new Bool(true)); - xFalse.and(yTrue).assertEquals(new Bool(false)); - }); - }).not.toThrow(); + xFalse.and(yTrue).assertEquals(new Bool(false)); + }); }); it('should throw if false "and" true is compared to true', async () => { - expect(() => { + await expect( Provable.runAndCheck(() => { const xFalse = Provable.witness(Bool, () => new Bool(false)); const yTrue = Provable.witness(Bool, () => new Bool(true)); xFalse.and(yTrue).assertEquals(new Bool(true)); - }); - }).toThrow(); + }) + ).rejects.toThrow(); }); }); describe('not', () => { it('should return true', async () => { - expect(() => { - Provable.runAndCheck(() => { - const xTrue = Provable.witness(Bool, () => new Bool(true)); - xTrue.toField().assertEquals(new Field(1)); - }); - }).not.toThrow(); + await Provable.runAndCheck(() => { + const xTrue = Provable.witness(Bool, () => new Bool(true)); + xTrue.toField().assertEquals(new Field(1)); + }); }); it('should return a new bool that is the negation of the input', async () => { - expect(() => { - Provable.runAndCheck(() => { - const xTrue = Provable.witness(Bool, () => new Bool(true)); - const yFalse = Provable.witness(Bool, () => new Bool(false)); - xTrue.not().assertEquals(new Bool(false)); - yFalse.not().assertEquals(new Bool(true)); - }); - }).not.toThrow(); + await Provable.runAndCheck(() => { + const xTrue = Provable.witness(Bool, () => new Bool(true)); + const yFalse = Provable.witness(Bool, () => new Bool(false)); + xTrue.not().assertEquals(new Bool(false)); + yFalse.not().assertEquals(new Bool(true)); + }); }); it('should throw if input.not() is compared to input', async () => { - expect(() => { + expect( Provable.runAndCheck(() => { const xTrue = Provable.witness(Bool, () => new Bool(true)); xTrue.not().assertEquals(xTrue); - }); - }).toThrow(); + }) + ).rejects.toThrow(); }); }); describe('or', () => { it('true "or" true should return true', async () => { - expect(() => { - Provable.runAndCheck(() => { - const xTrue = Provable.witness(Bool, () => new Bool(true)); - const yTrue = Provable.witness(Bool, () => new Bool(true)); + await Provable.runAndCheck(() => { + const xTrue = Provable.witness(Bool, () => new Bool(true)); + const yTrue = Provable.witness(Bool, () => new Bool(true)); - xTrue.or(yTrue).assertEquals(new Bool(true)); - }); - }).not.toThrow(); + xTrue.or(yTrue).assertEquals(new Bool(true)); + }); }); it('should throw if true "or" true is compared to false', async () => { - expect(() => { + expect( Provable.runAndCheck(() => { const xTrue = Provable.witness(Bool, () => new Bool(true)); const yTrue = Provable.witness(Bool, () => new Bool(true)); xTrue.or(yTrue).assertEquals(new Bool(false)); - }); - }).toThrow(); + }) + ).rejects.toThrow(); }); it('false "or" false should return false', async () => { - expect(() => { - Provable.runAndCheck(() => { - const xFalse = Provable.witness(Bool, () => new Bool(false)); - const yFalse = Provable.witness(Bool, () => new Bool(false)); + await Provable.runAndCheck(() => { + const xFalse = Provable.witness(Bool, () => new Bool(false)); + const yFalse = Provable.witness(Bool, () => new Bool(false)); - xFalse.or(yFalse).assertEquals(new Bool(false)); - }); - }).not.toThrow(); + xFalse.or(yFalse).assertEquals(new Bool(false)); + }); }); it('should throw if false "or" false is compared to true', async () => { - expect(() => { + expect( Provable.runAndCheck(() => { const xFalse = Provable.witness(Bool, () => new Bool(false)); const yFalse = Provable.witness(Bool, () => new Bool(false)); xFalse.or(yFalse).assertEquals(new Bool(true)); - }); - }).toThrow(); + }) + ).rejects.toThrow(); }); it('false "or" true should return true', async () => { - expect(() => { - Provable.runAndCheck(() => { - const xFalse = Provable.witness(Bool, () => new Bool(false)); - const yTrue = Provable.witness(Bool, () => new Bool(true)); + await Provable.runAndCheck(() => { + const xFalse = Provable.witness(Bool, () => new Bool(false)); + const yTrue = Provable.witness(Bool, () => new Bool(true)); - xFalse.or(yTrue).assertEquals(new Bool(true)); - }); - }).not.toThrow(); + xFalse.or(yTrue).assertEquals(new Bool(true)); + }); }); }); describe('assertEquals', () => { it('should not throw on true "assertEqual" true', async () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Bool, () => new Bool(true)); + await Provable.runAndCheck(() => { + const x = Provable.witness(Bool, () => new Bool(true)); - x.assertEquals(x); - }); - }).not.toThrow(); + x.assertEquals(x); + }); }); it('should throw on true "assertEquals" false', async () => { - expect(() => { + expect( Provable.runAndCheck(() => { const x = Provable.witness(Bool, () => new Bool(true)); const y = Provable.witness(Bool, () => new Bool(false)); x.assertEquals(y); - }); - }).toThrow(); + }) + ).rejects.toThrow(); }); }); describe('equals', () => { it('should not throw on true "equals" true', async () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Bool, () => new Bool(true)); + await Provable.runAndCheck(() => { + const x = Provable.witness(Bool, () => new Bool(true)); - x.equals(x).assertEquals(true); - }); - }).not.toThrow(); + x.equals(x).assertEquals(true); + }); }); it('should throw on true "equals" false', async () => { - expect(() => { + expect( Provable.runAndCheck(() => { const x = Provable.witness(Bool, () => new Bool(true)); const y = Provable.witness(Bool, () => new Bool(false)); x.equals(y).assertEquals(true); - }); - }).toThrow(); + }) + ).rejects.toThrow(); }); }); }); diff --git a/src/lib/primitives.unit-test.ts b/src/lib/provable/test/primitives.unit-test.ts similarity index 79% rename from src/lib/primitives.unit-test.ts rename to src/lib/provable/test/primitives.unit-test.ts index 6344ddb9f9..2a0d7a78bc 100644 --- a/src/lib/primitives.unit-test.ts +++ b/src/lib/provable/test/primitives.unit-test.ts @@ -1,7 +1,7 @@ -import { Circuit, circuitMain } from './circuit.js'; -import { UInt64, UInt32 } from './int.js'; +import { Circuit, circuitMain } from '../../proof-system/circuit.js'; +import { UInt64, UInt32 } from '../int.js'; import { expect } from 'expect'; -import { Provable } from './provable.js'; +import { Provable } from '../provable.js'; class Primitives extends Circuit { @circuitMain diff --git a/src/lib/circuit_value.test.ts b/src/lib/provable/test/provable.test.ts similarity index 94% rename from src/lib/circuit_value.test.ts rename to src/lib/provable/test/provable.test.ts index 44a81d43fa..46ec4738e5 100644 --- a/src/lib/circuit_value.test.ts +++ b/src/lib/provable/test/provable.test.ts @@ -6,16 +6,16 @@ import { Field, PrivateKey, PublicKey, -} from 'snarkyjs'; +} from 'o1js'; -describe('circuit', () => { +describe('Provable', () => { it('Provable.if out of snark', () => { let x = Provable.if(Bool(false), Int64, Int64.from(-1), Int64.from(-2)); expect(x.toString()).toBe('-2'); }); - it('Provable.if in snark', () => { - Provable.runAndCheck(() => { + it('Provable.if in snark', async () => { + await Provable.runAndCheck(() => { let x = Provable.witness(Int64, () => Int64.from(-1)); let y = Provable.witness(Int64, () => Int64.from(-2)); let b = Provable.witness(Bool, () => Bool(true)); @@ -78,10 +78,10 @@ describe('circuit', () => { ).toThrow(/`mask` must have 0 or 1 true element, found 2/); }); - it('Provable.assertEqual', () => { + it('Provable.assertEqual', async () => { const FieldAndBool = Struct({ x: Field, b: Bool }); - Provable.runAndCheck(() => { + await Provable.runAndCheck(() => { let x = Provable.witness(Field, () => Field(1)); let b = Provable.witness(Bool, () => Bool(true)); @@ -108,7 +108,7 @@ describe('circuit', () => { }); }); - it('Provable.equal', () => { + it('Provable.equal', async () => { const FieldAndBool = Struct({ x: Field, b: Bool }); let pk1 = PublicKey.fromBase58( 'B62qoCHJ1dcGjKhdMTMuAytzRkLxRFUgq6YC5XSgmmxAt8r7FVi1DhT' @@ -123,7 +123,7 @@ describe('circuit', () => { }); } - Provable.runAndCheck(() => { + await Provable.runAndCheck(() => { let x = Provable.witness(Field, () => Field(1)); let b = Provable.witness(Bool, () => Bool(true)); let pk = Provable.witness(PublicKey, () => pk1); diff --git a/src/lib/provable/test/provable.unit-test.ts b/src/lib/provable/test/provable.unit-test.ts new file mode 100644 index 0000000000..efa4b3483f --- /dev/null +++ b/src/lib/provable/test/provable.unit-test.ts @@ -0,0 +1,23 @@ +import { it } from 'node:test'; +import { Provable } from '../provable.js'; +import { Field } from '../field.js'; +import { expect } from 'expect'; +import { exists } from '../core/exists.js'; + +await it('can witness large field array', async () => { + let N = 100_000; + let arr = Array(N).fill(0n); + + await Provable.runAndCheck(() => { + // with exists + let fields = exists(N, () => arr); + + // with Provable.witness + let fields2 = Provable.witness(Provable.Array(Field, N), () => + arr.map(Field.from) + ); + + expect(fields.length).toEqual(N); + expect(fields2.length).toEqual(N); + }); +}); diff --git a/src/lib/provable/test/range-check.unit-test.ts b/src/lib/provable/test/range-check.unit-test.ts new file mode 100644 index 0000000000..fbb65befb7 --- /dev/null +++ b/src/lib/provable/test/range-check.unit-test.ts @@ -0,0 +1,157 @@ +import { mod } from '../../../bindings/crypto/finite-field.js'; +import { Field } from '../wrapped.js'; +import { ZkProgram } from '../../proof-system/zkprogram.js'; +import { + Spec, + boolean, + equivalentAsync, + fieldWithRng, +} from '../../testing/equivalent.js'; +import { Random } from '../../testing/property.js'; +import { assert } from '../gadgets/common.js'; +import { Gadgets } from '../gadgets/gadgets.js'; +import { l } from '../gadgets/range-check.js'; +import { + constraintSystem, + contains, + equals, + ifNotAllConstant, + withoutGenerics, +} from '../../testing/constraint-system.js'; + +let uint = (n: number | bigint): Spec => { + let uint = Random.bignat((1n << BigInt(n)) - 1n); + return fieldWithRng(uint); +}; + +let maybeUint = (n: number | bigint): Spec => { + let uint = Random.bignat((1n << BigInt(n)) - 1n); + return fieldWithRng( + Random.map(Random.oneOf(uint, uint.invalid), (x) => mod(x, Field.ORDER)) + ); +}; + +// constraint system sanity check + +constraintSystem( + 'range check 64', + { from: [Field] }, + Gadgets.rangeCheck64, + ifNotAllConstant(withoutGenerics(equals(['RangeCheck0']))) +); + +constraintSystem( + 'range check 8', + { from: [Field] }, + Gadgets.rangeCheck8, + ifNotAllConstant(withoutGenerics(equals(['EndoMulScalar', 'EndoMulScalar']))) +); + +constraintSystem( + 'multi-range check', + { from: [Field, Field, Field] }, + (x, y, z) => Gadgets.multiRangeCheck([x, y, z]), + ifNotAllConstant( + contains(['RangeCheck0', 'RangeCheck0', 'RangeCheck1', 'Zero']) + ) +); + +constraintSystem( + 'compact multi-range check', + { from: [Field, Field] }, + Gadgets.compactMultiRangeCheck, + ifNotAllConstant( + contains(['RangeCheck0', 'RangeCheck0', 'RangeCheck1', 'Zero']) + ) +); + +// TODO: make a ZkFunction or something that doesn't go through Pickles +// -------------------------- +// RangeCheck64 Gate +// -------------------------- + +let RangeCheck = ZkProgram({ + name: 'range-check', + methods: { + check64: { + privateInputs: [Field], + async method(x) { + Gadgets.rangeCheck64(x); + }, + }, + check8: { + privateInputs: [Field], + async method(x) { + Gadgets.rangeCheck8(x); + }, + }, + checkMulti: { + privateInputs: [Field, Field, Field], + async method(x, y, z) { + Gadgets.multiRangeCheck([x, y, z]); + }, + }, + checkCompact: { + privateInputs: [Field, Field], + async method(xy, z) { + let [x, y] = Gadgets.compactMultiRangeCheck(xy, z); + x.add(y.mul(1n << l)).assertEquals(xy); + }, + }, + }, +}); + +await RangeCheck.compile(); + +// TODO: we use this as a test because there's no way to check custom gates quickly :( +const runs = 2; + +await equivalentAsync({ from: [maybeUint(64)], to: boolean }, { runs })( + (x) => { + assert(x < 1n << 64n); + return true; + }, + async (x) => { + let proof = await RangeCheck.check64(x); + return await RangeCheck.verify(proof); + } +); + +await equivalentAsync({ from: [maybeUint(8)], to: boolean }, { runs })( + (x) => { + assert(x < 1n << 8n); + return true; + }, + async (x) => { + let proof = await RangeCheck.check8(x); + return await RangeCheck.verify(proof); + } +); + +await equivalentAsync( + { from: [maybeUint(l), uint(l), uint(l)], to: boolean }, + { runs } +)( + (x, y, z) => { + assert(!(x >> l) && !(y >> l) && !(z >> l), 'multi: not out of range'); + return true; + }, + async (x, y, z) => { + let proof = await RangeCheck.checkMulti(x, y, z); + return await RangeCheck.verify(proof); + } +); + +await equivalentAsync( + { from: [maybeUint(2n * l), uint(l)], to: boolean }, + { runs } +)( + (xy, z) => { + assert(!(xy >> (2n * l)) && !(z >> l), 'compact: not out of range'); + return true; + }, + async (xy, z) => { + let proof = await RangeCheck.checkCompact(xy, z); + return await RangeCheck.verify(proof); + } +); diff --git a/src/lib/scalar.test.ts b/src/lib/provable/test/scalar.test.ts similarity index 80% rename from src/lib/scalar.test.ts rename to src/lib/provable/test/scalar.test.ts index d3e80a378c..caf418c362 100644 --- a/src/lib/scalar.test.ts +++ b/src/lib/provable/test/scalar.test.ts @@ -1,57 +1,41 @@ -import { shutdown, isReady, Field, Bool, Provable, Scalar } from 'snarkyjs'; +import { Field, Provable, Scalar } from 'o1js'; describe('scalar', () => { - beforeAll(async () => { - await isReady; - }); - - afterAll(async () => { - setTimeout(async () => { - await shutdown(); - }, 0); - }); - describe('scalar', () => { describe('Inside circuit', () => { describe('toFields', () => { - it('should return an array of Fields', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(Scalar, () => Scalar.random()); - const fieldArr = x.toFields(); - expect(Array.isArray(fieldArr)).toBe(true); - }); - }).not.toThrow(); + it('should return an array of Fields', async () => { + await Provable.runAndCheck(() => { + const x = Provable.witness(Scalar, () => Scalar.random()); + const fieldArr = x.toFields(); + expect(Array.isArray(fieldArr)).toBe(true); + }); }); }); describe('toFields / fromFields', () => { - it('should return the same', () => { - expect(() => { - let s0 = Scalar.random(); - Provable.runAndCheck(() => { - let s1 = Provable.witness(Scalar, () => s0); - Provable.assertEqual(Scalar.fromFields(s1.toFields()), s0); - }); - }).not.toThrow(); + it('should return the same', async () => { + let s0 = Scalar.random(); + await Provable.runAndCheck(() => { + let s1 = Provable.witness(Scalar, () => s0); + Provable.assertEqual(Scalar.fromFields(s1.toFields()), s0); + }); }); }); describe('fromBits', () => { - it('should return a Scalar', () => { - expect(() => { - Provable.runAndCheck(() => { - Provable.witness(Scalar, () => - Scalar.fromBits(Field.random().toBits()) - ); - }); - }).not.toThrow(); + it('should return a Scalar', async () => { + await Provable.runAndCheck(() => { + Provable.witness(Scalar, () => + Scalar.fromBits(Field.random().toBits()) + ); + }); }); }); describe('random', () => { - it('two different calls should be different', () => { - Provable.runAndCheck(() => { + it('two different calls should be different', async () => { + await Provable.runAndCheck(() => { const x = Provable.witness(Scalar, () => Scalar.random()); const y = Provable.witness(Scalar, () => Scalar.random()); expect(x).not.toEqual(y); diff --git a/src/lib/provable/test/sha256.unit-test.ts b/src/lib/provable/test/sha256.unit-test.ts new file mode 100644 index 0000000000..c18d661752 --- /dev/null +++ b/src/lib/provable/test/sha256.unit-test.ts @@ -0,0 +1,78 @@ +import { ZkProgram } from '../../proof-system/zkprogram.js'; +import { Bytes } from '../wrapped-classes.js'; +import { Gadgets } from '../gadgets/gadgets.js'; +import { sha256 as nobleSha256 } from '@noble/hashes/sha256'; +import { bytes } from './test-utils.js'; +import { + equivalentAsync, + equivalentProvable, +} from '../../testing/equivalent.js'; +import { Random, sample } from '../../testing/random.js'; +import { expect } from 'expect'; + +sample(Random.nat(400), 5).forEach((preimageLength) => { + let inputBytes = bytes(preimageLength); + let outputBytes = bytes(256 / 8); + + equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( + (x) => nobleSha256(x), + (x) => Gadgets.SHA256.hash(x), + `sha256 preimage length ${preimageLength}` + ); +}); + +const Sha256Program = ZkProgram({ + name: `sha256`, + publicOutput: Bytes(32).provable, + methods: { + sha256: { + privateInputs: [Bytes(192).provable], + async method(preImage: Bytes) { + return Gadgets.SHA256.hash(preImage); + }, + }, + }, +}); + +const RUNS = 2; + +await Sha256Program.compile(); + +await equivalentAsync( + { + from: [bytes(192)], + to: bytes(32), + }, + { runs: RUNS } +)(nobleSha256, async (x) => { + const proof = await Sha256Program.sha256(x); + await Sha256Program.verify(proof); + return proof.publicOutput; +}); + +for (let { preimage, hash } of testVectors()) { + let actual = Gadgets.SHA256.hash(Bytes.fromString(preimage)); + expect(actual.toHex()).toEqual(hash); +} + +function testVectors() { + return [ + { + preimage: 'abc', + hash: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', + }, + { + preimage: 'a'.repeat(1000000), + hash: 'cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0', + }, + { + preimage: '', + hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + { + preimage: + 'de188941a3375d3a8a061e67576e926dc71a7fa3f0cceb97452b4d3227965f9ea8cc75076d9fb9c5417aa5cb30fc22198b34982dbb629e', + hash: '70b6ee0dd06c26d51177d5bb1de954d6d50aa9f7b771b4401415d43da40605ad', + }, + ]; +} diff --git a/src/lib/string.test.ts b/src/lib/provable/test/string.test.ts similarity index 85% rename from src/lib/string.test.ts rename to src/lib/provable/test/string.test.ts index 02e2ba83f9..806e9db3fd 100644 --- a/src/lib/string.test.ts +++ b/src/lib/provable/test/string.test.ts @@ -1,19 +1,8 @@ -import { - Bool, - Character, - Provable, - CircuitString, - Field, - shutdown, - isReady, -} from 'snarkyjs'; +import { Bool, Character, Provable, CircuitString, Field } from 'o1js'; describe('Circuit String', () => { - beforeEach(() => isReady); - afterAll(() => setTimeout(shutdown, 0)); - describe('#equals', () => { - test('returns true when values are equal', () => { + test('returns true when values are equal', async () => { const str = CircuitString.fromString( 'Everything we hear is an opinion, not a fact. Everything we see is a perspective, not the truth' ); @@ -22,7 +11,7 @@ describe('Circuit String', () => { ); expect(str.equals(same_str)).toEqual(Bool(true)); - Provable.runAndCheck(() => { + await Provable.runAndCheck(() => { const str = CircuitString.fromString( 'Everything we hear is an opinion, not a fact. Everything we see is a perspective, not the truth' ); @@ -33,12 +22,12 @@ describe('Circuit String', () => { }); }); - test('returns false when values are not equal', () => { + test('returns false when values are not equal', async () => { const str = CircuitString.fromString('Your size'); const not_same_str = CircuitString.fromString('size'); expect(str.equals(not_same_str)).toEqual(Bool(false)); - Provable.runAndCheck(() => { + await Provable.runAndCheck(() => { const str = Provable.witness(CircuitString, () => { return CircuitString.fromString('Your size'); }); @@ -62,7 +51,7 @@ describe('Circuit String', () => { ); expect(str.contains(contained_str)).toEqual(new Bool(true)); - Provable.runAndCheck(() => { + await Provable.runAndCheck(() => { const str = CircuitString.fromString( 'Everything we hear is an opinion, not a fact. Everything we see is a perspective, not the truth' ); @@ -78,7 +67,7 @@ describe('Circuit String', () => { const not_contained_str = CircuitString.fromString('defhij'); expect(str.contains(not_contained_str)).toEqual(new Bool(false)); - Provable.runAndCheck(() => { + await Provable.runAndCheck(() => { const str = CircuitString.fromString('abcdefghijklmnop'); const not_contained_str = CircuitString.fromString('defhij'); expect(str.contains(not_contained_str)).toEqual(new Bool(false)); @@ -91,7 +80,7 @@ describe('Circuit String', () => { const contained_str = CircuitString.fromString('ab'); expect(str.contains(contained_str)).toEqual(new Bool(true)); - Provable.runAndCheck(() => { + await Provable.runAndCheck(() => { const str = CircuitString8.fromString('abcd'); const contained_str = CircuitString.fromString('ab'); expect(str.contains(contained_str)).toEqual(new Bool(true)); @@ -103,7 +92,7 @@ describe('Circuit String', () => { const contained_str = CircuitString8.fromString('ab'); expect(str.contains(contained_str)).toEqual(new Bool(true)); - Provable.runAndCheck(() => { + await Provable.runAndCheck(() => { const str = CircuitString.fromString('abcd'); const contained_str = CircuitString8.fromString('ab'); expect(str.contains(contained_str)).toEqual(new Bool(true)); @@ -113,13 +102,13 @@ describe('Circuit String', () => { }); */ describe('#toString', () => { - test('serializes to string', () => { + test('serializes to string', async () => { const js_str = 'Everything we hear is an opinion, not a fact. Everything we see is a perspective, not the truth'; const str = CircuitString.fromString(js_str); expect(str.toString()).toBe(js_str); - Provable.runAndCheck(() => { + await Provable.runAndCheck(() => { const js_str = 'Everything we hear is an opinion, not a fact. Everything we see is a perspective, not the truth'; const str = CircuitString.fromString(js_str); @@ -129,7 +118,7 @@ describe('Circuit String', () => { }); describe('#substring', () => { - test('selects substring', () => { + test('selects substring', async () => { const str = CircuitString.fromString( 'Everything we hear is an opinion, not a fact. Everything we see is a perspective, not the truth' ); @@ -137,7 +126,7 @@ describe('Circuit String', () => { 'Everything we see is a perspective' ); - Provable.runAndCheck(() => { + await Provable.runAndCheck(() => { const str = CircuitString.fromString( 'Everything we hear is an opinion, not a fact. Everything we see is a perspective, not the truth' ); @@ -149,12 +138,12 @@ describe('Circuit String', () => { }); describe('#append', () => { - test('appends 2 strings', () => { + test('appends 2 strings', async () => { const str1 = CircuitString.fromString('abcd'); const str2 = CircuitString.fromString('efgh'); expect(str1.append(str2).toString()).toBe('abcdefgh'); - Provable.runAndCheck(() => { + await Provable.runAndCheck(() => { const str1 = CircuitString.fromString('abcd'); const str2 = CircuitString.fromString('efgh'); expect(str1.append(str2).toString()).toBe('abcdefgh'); @@ -162,10 +151,10 @@ describe('Circuit String', () => { }); }); - /* describe('CircuitString8', () => { + /* describe('CircuitString8', async () => { test('cannot create more than 8 chars', () => { expect(() => { - Provable.runAndCheck(() => { + await Provable.runAndCheck(() => { Provable.witness(CircuitString8, () => { return CircuitString8.fromString('More than eight chars'); }); @@ -175,9 +164,9 @@ describe('Circuit String', () => { }); */ describe('with invalid input', () => { - test.skip('cannot use a character out of range', () => { - expect(() => { - Provable.runAndCheck(() => { + test.skip('cannot use a character out of range', async () => { + await expect(async () => { + await Provable.runAndCheck(() => { const str = Provable.witness(CircuitString, () => { return CircuitString.fromCharacters([ new Character(Field(100)), @@ -186,7 +175,7 @@ describe('Circuit String', () => { ]); }); }); - }).toThrow(); + }).rejects.toThrow(); }); }); }); diff --git a/src/lib/circuit_value.unit-test.ts b/src/lib/provable/test/struct.unit-test.ts similarity index 67% rename from src/lib/circuit_value.unit-test.ts rename to src/lib/provable/test/struct.unit-test.ts index 6372c52a7f..7218d99bf8 100644 --- a/src/lib/circuit_value.unit-test.ts +++ b/src/lib/provable/test/struct.unit-test.ts @@ -1,13 +1,18 @@ -import { provable, Struct } from './circuit_value.js'; -import { UInt32 } from './int.js'; -import { PrivateKey, PublicKey } from './signature.js'; +import { provable, Struct } from '../types/struct.js'; +import { Unconstrained } from '../types/unconstrained.js'; +import { UInt32 } from '../int.js'; +import { PrivateKey, PublicKey } from '../crypto/signature.js'; import { expect } from 'expect'; -import { method, SmartContract } from './zkapp.js'; -import { LocalBlockchain, setActiveInstance, transaction } from './mina.js'; -import { State, state } from './state.js'; -import { AccountUpdate } from './account_update.js'; -import { Provable } from './provable.js'; -import { Field } from './core.js'; +import { method, SmartContract } from '../../mina/zkapp.js'; +import { + LocalBlockchain, + setActiveInstance, + transaction, +} from '../../mina/mina.js'; +import { State, state } from '../../mina/state.js'; +import { AccountUpdate } from '../../mina/account-update.js'; +import { Provable } from '../provable.js'; +import { Field } from '../wrapped.js'; let type = provable({ nested: { a: Number, b: Boolean }, @@ -60,23 +65,24 @@ let restored = type.fromFields(fields, aux); expect(JSON.stringify(restored)).toEqual(original); // check -Provable.runAndCheck(() => { +await Provable.runAndCheck(() => { type.check(value); }); // should fail `check` if `check` of subfields doesn't pass -expect(() => + +// manually construct an invalid uint32 +let noUint32 = new UInt32(1); +noUint32.value = Field(-1); + +await expect(() => Provable.runAndCheck(() => { let x = Provable.witness(type, () => ({ ...value, - uint: [ - UInt32.zero, - // invalid Uint32 - new UInt32(Field(-1)), - ], + uint: [UInt32.zero, noUint32], })); }) -).toThrow(`Constraint unsatisfied`); +).rejects.toThrow(`Constraint unsatisfied`); // class version of `provable` class MyStruct extends Struct({ @@ -96,6 +102,7 @@ class MyStructPure extends Struct({ class MyTuple extends Struct([PublicKey, String]) {} let targetString = 'some particular string'; +let targetBigint = 99n; let gotTargetString = false; // create a smart contract and pass auxiliary data to a method @@ -106,19 +113,31 @@ class MyContract extends SmartContract { // this works because MyStructPure only contains field elements @state(MyStructPure) x = State(); - @method myMethod(value: MyStruct, tuple: MyTuple, update: AccountUpdate) { + @method async myMethod( + value: MyStruct, + tuple: MyTuple, + update: AccountUpdate, + unconstrained: Unconstrained + ) { // check if we can pass in string values if (value.other === targetString) gotTargetString = true; value.uint[0].assertEquals(UInt32.zero); + // cannot access unconstrained values in provable code + if (Provable.inCheckedComputation()) + expect(() => unconstrained.get()).toThrow( + 'You cannot use Unconstrained.get() in provable code.' + ); + Provable.asProver(() => { let err = 'wrong value in prover'; if (tuple[1] !== targetString) throw Error(err); // check if we can pass in account updates if (update.lazyAuthorization?.kind !== 'lazy-signature') throw Error(err); - if (update.lazyAuthorization.privateKey?.toBase58() !== key.toBase58()) - throw Error(err); + + // check if we can pass in unconstrained values + if (unconstrained.get() !== targetBigint) throw Error(err); }); } } @@ -130,10 +149,10 @@ let key = PrivateKey.random(); let address = key.toPublicKey(); let contract = new MyContract(address); -let tx = await transaction(() => { - let accountUpdate = AccountUpdate.createSigned(key); +let tx = await transaction(async () => { + let accountUpdate = AccountUpdate.createSigned(address); - contract.myMethod( + await contract.myMethod( { nested: { a: 1, b: false }, other: targetString, @@ -141,7 +160,8 @@ let tx = await transaction(() => { uint: [UInt32.from(0), UInt32.from(10)], }, [address, targetString], - accountUpdate + accountUpdate, + Unconstrained.from(targetBigint) ); }); diff --git a/src/lib/provable/test/test-utils.ts b/src/lib/provable/test/test-utils.ts new file mode 100644 index 0000000000..0406cc0d2a --- /dev/null +++ b/src/lib/provable/test/test-utils.ts @@ -0,0 +1,69 @@ +import type { FiniteField } from '../../../bindings/crypto/finite-field.js'; +import { ProvableSpec, spec } from '../../testing/equivalent.js'; +import { Random } from '../../testing/random.js'; +import { Field3 } from '../gadgets/gadgets.js'; +import { assert } from '../gadgets/common.js'; +import { Bytes } from '../wrapped-classes.js'; + +export { + foreignField, + unreducedForeignField, + uniformForeignField, + bytes, + throwError, +}; + +// test input specs + +function foreignField(F: FiniteField): ProvableSpec { + return { + rng: Random.otherField(F), + there: Field3.from, + back: Field3.toBigint, + provable: Field3.provable, + }; +} + +// for testing with inputs > f +function unreducedForeignField( + maxBits: number, + F: FiniteField +): ProvableSpec { + return { + rng: Random.bignat(1n << BigInt(maxBits)), + there: Field3.from, + back: Field3.toBigint, + provable: Field3.provable, + assertEqual(x, y, message) { + // need weak equality here because, while ffadd works on bigints larger than the modulus, + // it can't fully reduce them + assert(F.equal(x, y), message); + }, + }; +} + +// for fields that must follow an unbiased distribution, like private keys +function uniformForeignField(F: FiniteField): ProvableSpec { + return { + rng: Random(F.random), + there: Field3.from, + back: Field3.toBigint, + provable: Field3.provable, + }; +} + +function bytes(length: number) { + const Bytes_ = Bytes(length); + return spec({ + rng: Random.map(Random.bytes(length), (x) => Uint8Array.from(x)), + there: Bytes_.from, + back: (x) => x.toBytes(), + provable: Bytes_.provable, + }); +} + +// helper + +function throwError(message: string): T { + throw Error(message); +} diff --git a/src/lib/provable/types/auxiliary.ts b/src/lib/provable/types/auxiliary.ts new file mode 100644 index 0000000000..d9c1c76dd2 --- /dev/null +++ b/src/lib/provable/types/auxiliary.ts @@ -0,0 +1,13 @@ +import type { ProvableHashable } from '../crypto/poseidon.js'; + +export { RandomId }; + +const RandomId: ProvableHashable = { + sizeInFields: () => 0, + toFields: () => [], + toAuxiliary: (v = Math.random()) => [v], + fromFields: (_, [v]) => v, + check: () => {}, + toInput: () => ({}), + empty: () => Math.random(), +}; diff --git a/src/lib/provable/types/circuit-value.ts b/src/lib/provable/types/circuit-value.ts new file mode 100644 index 0000000000..828a0b0776 --- /dev/null +++ b/src/lib/provable/types/circuit-value.ts @@ -0,0 +1,221 @@ +import 'reflect-metadata'; +import { Field } from '../wrapped.js'; +import { HashInput, NonMethods } from './provable-derivers.js'; +import { Provable } from '../provable.js'; +import { AnyConstructor, FlexibleProvable } from './struct.js'; + +export { CircuitValue, prop, arrayProp }; + +/** + * @deprecated `CircuitValue` is deprecated in favor of {@link Struct}, which features a simpler API and better typing. + */ +abstract class CircuitValue { + constructor(...props: any[]) { + // if this is called with no arguments, do nothing, to support simple super() calls + if (props.length === 0) return; + + let fields = this.constructor.prototype._fields; + if (fields === undefined) return; + if (props.length !== fields.length) { + throw Error( + `${this.constructor.name} constructor called with ${props.length} arguments, but expected ${fields.length}` + ); + } + for (let i = 0; i < fields.length; ++i) { + let [key] = fields[i]; + (this as any)[key] = props[i]; + } + } + + static fromObject( + this: T, + value: NonMethods> + ): InstanceType { + return Object.assign(Object.create(this.prototype), value); + } + + static sizeInFields(): number { + const fields: [string, any][] = (this as any).prototype._fields; + return fields.reduce((acc, [_, typ]) => acc + typ.sizeInFields(), 0); + } + + static toFields( + this: T, + v: InstanceType + ): Field[] { + const res: Field[] = []; + const fields = this.prototype._fields; + if (fields === undefined || fields === null) { + return res; + } + for (let i = 0, n = fields.length; i < n; ++i) { + const [key, propType] = fields[i]; + const subElts: Field[] = propType.toFields((v as any)[key]); + subElts.forEach((x) => res.push(x)); + } + return res; + } + + static toAuxiliary(): [] { + return []; + } + + static toInput( + this: T, + v: InstanceType + ): HashInput { + let input: HashInput = { fields: [], packed: [] }; + let fields = this.prototype._fields; + if (fields === undefined) return input; + for (let i = 0, n = fields.length; i < n; ++i) { + let [key, type] = fields[i]; + if ('toInput' in type) { + input = HashInput.append(input, type.toInput(v[key])); + continue; + } + // as a fallback, use toFields on the type + // TODO: this is problematic -- ignores if there's a toInput on a nested type + // so, remove this? should every provable define toInput? + let xs: Field[] = type.toFields(v[key]); + input.fields!.push(...xs); + } + return input; + } + + toFields(): Field[] { + return (this.constructor as any).toFields(this); + } + + toJSON(): any { + return (this.constructor as any).toJSON(this); + } + + toConstant(): this { + return (this.constructor as any).toConstant(this); + } + + equals(x: this) { + return Provable.equal(this, x); + } + + assertEquals(x: this) { + Provable.assertEqual(this, x); + } + + isConstant() { + return this.toFields().every((x) => x.isConstant()); + } + + static fromFields( + this: T, + xs: Field[] + ): InstanceType { + const fields: [string, any][] = (this as any).prototype._fields; + if (xs.length < fields.length) { + throw Error( + `${this.name}.fromFields: Expected ${fields.length} field elements, got ${xs?.length}` + ); + } + let offset = 0; + const props: any = {}; + for (let i = 0; i < fields.length; ++i) { + const [key, propType] = fields[i]; + const propSize = propType.sizeInFields(); + const propVal = propType.fromFields( + xs.slice(offset, offset + propSize), + [] + ); + props[key] = propVal; + offset += propSize; + } + return Object.assign(Object.create(this.prototype), props); + } + + static check(this: T, v: InstanceType) { + const fields = (this as any).prototype._fields; + if (fields === undefined || fields === null) { + return; + } + for (let i = 0; i < fields.length; ++i) { + const [key, propType] = fields[i]; + const value = (v as any)[key]; + if (propType.check === undefined) + throw Error('bug: CircuitValue without .check()'); + propType.check(value); + } + } + + static toConstant( + this: T, + t: InstanceType + ): InstanceType { + const xs: Field[] = (this as any).toFields(t); + return (this as any).fromFields(xs.map((x) => x.toConstant())); + } + + static toJSON(this: T, v: InstanceType) { + const res: any = {}; + if ((this as any).prototype._fields !== undefined) { + const fields: [string, any][] = (this as any).prototype._fields; + fields.forEach(([key, propType]) => { + res[key] = propType.toJSON((v as any)[key]); + }); + } + return res; + } + + static fromJSON( + this: T, + value: any + ): InstanceType { + let props: any = {}; + let fields: [string, any][] = (this as any).prototype._fields; + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + throw Error(`${this.name}.fromJSON(): invalid input ${value}`); + } + if (fields !== undefined) { + for (let i = 0; i < fields.length; ++i) { + let [key, propType] = fields[i]; + if (value[key] === undefined) { + throw Error(`${this.name}.fromJSON(): invalid input ${value}`); + } else { + props[key] = propType.fromJSON(value[key]); + } + } + } + return Object.assign(Object.create(this.prototype), props); + } + + static empty(): InstanceType { + const fields: [string, any][] = (this as any).prototype._fields ?? []; + let props: any = {}; + fields.forEach(([key, propType]) => { + props[key] = propType.empty(); + }); + return Object.assign(Object.create(this.prototype), props); + } +} + +function prop(this: any, target: any, key: string) { + const fieldType = Reflect.getMetadata('design:type', target, key); + if (!target.hasOwnProperty('_fields')) { + target._fields = []; + } + if (fieldType === undefined) { + } else if (fieldType.toFields && fieldType.fromFields) { + target._fields.push([key, fieldType]); + } else { + console.log( + `warning: property ${key} missing field element conversion methods` + ); + } +} + +function arrayProp(elementType: FlexibleProvable, length: number) { + return function (target: any, key: string) { + if (!target.hasOwnProperty('_fields')) { + target._fields = []; + } + target._fields.push([key, Provable.Array(elementType, length)]); + }; +} diff --git a/src/lib/provable/types/fields.ts b/src/lib/provable/types/fields.ts new file mode 100644 index 0000000000..d020ac6fb3 --- /dev/null +++ b/src/lib/provable/types/fields.ts @@ -0,0 +1,46 @@ +import { ProvablePureExtended } from './struct.js'; +import type { Field } from '../field.js'; +import { createField, getField } from '../core/field-constructor.js'; + +export { modifiedField, fields }; + +// provable for a single field element + +const ProvableField: ProvablePureExtended = { + sizeInFields: () => 1, + toFields: (x) => [x], + toAuxiliary: () => [], + fromFields: ([x]) => x, + check: () => {}, + toInput: (x) => ({ fields: [x] }), + toJSON: (x) => getField().toJSON(x), + fromJSON: (x) => getField().fromJSON(x), + empty: () => createField(0), +}; + +function modifiedField( + methods: Partial> +): ProvablePureExtended { + return Object.assign({}, ProvableField, methods); +} + +// provable for a fixed-size array of field elements + +let id = (t: T) => t; + +function fields(length: number): ProvablePureExtended { + return { + sizeInFields: () => length, + toFields: id, + toAuxiliary: () => [], + fromFields: id, + check: () => {}, + toInput: (x) => ({ fields: x }), + toJSON: (x) => x.map(getField().toJSON), + fromJSON: (x) => x.map(getField().fromJSON), + empty: () => { + let zero = createField(0); + return new Array(length).fill(zero); + }, + }; +} diff --git a/src/lib/provable/types/provable-derivers.ts b/src/lib/provable/types/provable-derivers.ts new file mode 100644 index 0000000000..9e9844056a --- /dev/null +++ b/src/lib/provable/types/provable-derivers.ts @@ -0,0 +1,102 @@ +import { Provable, ProvablePure } from './provable-intf.js'; +import type { Field } from '../wrapped.js'; +import { + createDerivers, + NonMethods, + InferProvable as GenericInferProvable, + InferJson, + InferredProvable as GenericInferredProvable, + IsPure as GenericIsPure, + createHashInput, + Constructor, +} from '../../../bindings/lib/provable-generic.js'; +import { Tuple } from '../../util/types.js'; +import { GenericHashInput } from '../../../bindings/lib/generic.js'; + +// external API +export { + ProvableExtended, + provable, + provablePure, + provableTuple, + provableFromClass, +}; + +// internal API +export { + NonMethods, + HashInput, + InferProvable, + InferJson, + InferredProvable, + IsPure, +}; + +type ProvableExtension = { + toInput: (x: T) => { fields?: Field[]; packed?: [Field, number][] }; + toJSON: (x: T) => TJson; + fromJSON: (x: TJson) => T; + empty: () => T; +}; +type ProvableExtended = Provable & + ProvableExtension; +type ProvablePureExtended = ProvablePure & + ProvableExtension; + +type InferProvable = GenericInferProvable; +type InferredProvable = GenericInferredProvable; +type IsPure = GenericIsPure; + +type HashInput = GenericHashInput; +const HashInput = createHashInput(); + +const { provable } = createDerivers(); + +function provablePure( + typeObj: A +): ProvablePureExtended, InferJson> { + return provable(typeObj, { isPure: true }) as any; +} + +function provableTuple>(types: T): InferredProvable { + return provable(types) as any; +} + +function provableFromClass>( + Class: Constructor & { check?: (x: T) => void; empty?: () => T }, + typeObj: A +): IsPure extends true + ? ProvablePureExtended> + : ProvableExtended> { + let raw = provable(typeObj); + return { + sizeInFields: raw.sizeInFields, + toFields: raw.toFields, + toAuxiliary: raw.toAuxiliary, + fromFields(fields, aux) { + return construct(Class, raw.fromFields(fields, aux)); + }, + check(value) { + if (Class.check !== undefined) { + Class.check(value); + } else { + raw.check(value); + } + }, + toInput: raw.toInput, + toJSON: raw.toJSON, + fromJSON(x) { + return construct(Class, raw.fromJSON(x)); + }, + empty() { + return Class.empty !== undefined + ? Class.empty() + : construct(Class, raw.empty()); + }, + } satisfies ProvableExtended> as any; +} + +function construct(Class: Constructor, value: Raw): T { + let instance = Object.create(Class.prototype); + return Object.assign(instance, value); +} diff --git a/src/lib/provable/types/provable-intf.ts b/src/lib/provable/types/provable-intf.ts new file mode 100644 index 0000000000..89d3f63059 --- /dev/null +++ b/src/lib/provable/types/provable-intf.ts @@ -0,0 +1,78 @@ +import type { Field } from '../field.js'; + +export { Provable, ProvablePure }; + +/** + * `Provable` is the general interface for provable types in o1js. + * + * `Provable` describes how a type `T` is made up of {@link Field} elements and "auxiliary" (non-provable) data. + * + * `Provable` is the required input type in several methods in o1js. + * One convenient way to create a `Provable` is using `Struct`. + * + * All built-in provable types in o1js ({@link Field}, {@link Bool}, etc.) are instances of `Provable` as well. + * + * Note: These methods are meant to be used by the library internally and are not directly when writing provable code. + */ +type Provable = { + /** + * A function that takes `value`, an element of type `T`, as argument and returns + * an array of {@link Field} elements that make up the provable data of `value`. + * + * @param value - the element of type `T` to generate the {@link Field} array from. + * + * @return A {@link Field} array describing how this `T` element is made up of {@link Field} elements. + */ + toFields: (value: T) => Field[]; + + /** + * A function that takes `value` (optional), an element of type `T`, as argument and + * returns an array of any type that make up the "auxiliary" (non-provable) data of `value`. + * + * @param value - the element of type `T` to generate the auxiliary data array from, optional. + * If not provided, a default value for auxiliary data is returned. + * + * @return An array of any type describing how this `T` element is made up of "auxiliary" (non-provable) data. + */ + toAuxiliary: (value?: T) => any[]; + + /** + * A function that returns an element of type `T` from the given provable and "auxiliary" data. + * + * This function is the reverse operation of calling {@link toFields} and {@link toAuxilary} methods on an element of type `T`. + * + * @param fields - an array of {@link Field} elements describing the provable data of the new `T` element. + * @param aux - an array of any type describing the "auxiliary" data of the new `T` element, optional. + * + * @return An element of type `T` generated from the given provable and "auxiliary" data. + */ + fromFields: (fields: Field[], aux: any[]) => T; + + /** + * Return the size of the `T` type in terms of {@link Field} type, as {@link Field} is the primitive type. + * + * @return A `number` representing the size of the `T` type in terms of {@link Field} type. + */ + sizeInFields(): number; + + /** + * Add assertions to the proof to check if `value` is a valid member of type `T`. + * This function does not return anything, instead it creates any number of assertions to prove that `value` is a valid member of the type `T`. + * + * For instance, calling check function on the type {@link Bool} asserts that the value of the element is either 1 or 0. + * + * @param value - the element of type `T` to put assertions on. + */ + check: (value: T) => void; +}; + +/** + * `ProvablePure` is a special kind of {@link Provable} interface, where the "auxiliary" (non-provable) data is empty. + * This means the type consists only of field elements, in that sense it is "pure". + * Any instance of `ProvablePure` is also an instance of `Provable` where the "auxiliary" data is empty. + * + * Examples where `ProvablePure` is required are types of on-chain state, events and actions. + */ +type ProvablePure = Omit, 'fromFields'> & { + fromFields: (fields: Field[]) => T; +}; diff --git a/src/lib/circuit_value.ts b/src/lib/provable/types/struct.ts similarity index 56% rename from src/lib/circuit_value.ts rename to src/lib/provable/types/struct.ts index 3c990ee37f..250f05b7d3 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/provable/types/struct.ts @@ -1,28 +1,25 @@ -import 'reflect-metadata'; -import { ProvablePure } from '../snarky.js'; -import { Field, Bool, Scalar, Group } from './core.js'; +import { Field, Bool, Scalar, Group } from '../wrapped.js'; import { provable, provablePure, + provableTuple, HashInput, NonMethods, -} from '../bindings/lib/provable-snarky.js'; +} from './provable-derivers.js'; import type { InferJson, InferProvable, InferredProvable, IsPure, -} from '../bindings/lib/provable-snarky.js'; -import { Provable } from './provable.js'; +} from './provable-derivers.js'; +import { Provable } from '../provable.js'; +import { Proof } from '../../proof-system/zkprogram.js'; +import { ProvablePure } from './provable-intf.js'; // external API export { - CircuitValue, ProvableExtended, ProvablePureExtended, - prop, - arrayProp, - matrixProp, provable, provablePure, Struct, @@ -32,21 +29,22 @@ export { // internal API export { + provableTuple, AnyConstructor, cloneCircuitValue, circuitValueEquals, - toConstant, - isConstant, InferProvable, HashInput, InferJson, InferredProvable, + StructNoJson, }; type ProvableExtension = { toInput: (x: T) => { fields?: Field[]; packed?: [Field, number][] }; toJSON: (x: T) => TJson; fromJSON: (x: TJson) => T; + empty: () => T; }; type ProvableExtended = Provable & @@ -66,228 +64,7 @@ type Constructor = new (...args: any) => T; type AnyConstructor = Constructor; /** - * @deprecated `CircuitValue` is deprecated in favor of {@link Struct}, which features a simpler API and better typing. - */ -abstract class CircuitValue { - constructor(...props: any[]) { - // if this is called with no arguments, do nothing, to support simple super() calls - if (props.length === 0) return; - - let fields = this.constructor.prototype._fields; - if (fields === undefined) return; - if (props.length !== fields.length) { - throw Error( - `${this.constructor.name} constructor called with ${props.length} arguments, but expected ${fields.length}` - ); - } - for (let i = 0; i < fields.length; ++i) { - let [key] = fields[i]; - (this as any)[key] = props[i]; - } - } - - static fromObject( - this: T, - value: NonMethods> - ): InstanceType { - return Object.assign(Object.create(this.prototype), value); - } - - static sizeInFields(): number { - const fields: [string, any][] = (this as any).prototype._fields; - return fields.reduce((acc, [_, typ]) => acc + typ.sizeInFields(), 0); - } - - static toFields( - this: T, - v: InstanceType - ): Field[] { - const res: Field[] = []; - const fields = this.prototype._fields; - if (fields === undefined || fields === null) { - return res; - } - for (let i = 0, n = fields.length; i < n; ++i) { - const [key, propType] = fields[i]; - const subElts: Field[] = propType.toFields((v as any)[key]); - subElts.forEach((x) => res.push(x)); - } - return res; - } - - static toAuxiliary(): [] { - return []; - } - - static toInput( - this: T, - v: InstanceType - ): HashInput { - let input: HashInput = { fields: [], packed: [] }; - let fields = this.prototype._fields; - if (fields === undefined) return input; - for (let i = 0, n = fields.length; i < n; ++i) { - let [key, type] = fields[i]; - if ('toInput' in type) { - input = HashInput.append(input, type.toInput(v[key])); - continue; - } - // as a fallback, use toFields on the type - // TODO: this is problematic -- ignores if there's a toInput on a nested type - // so, remove this? should every provable define toInput? - let xs: Field[] = type.toFields(v[key]); - input.fields!.push(...xs); - } - return input; - } - - toFields(): Field[] { - return (this.constructor as any).toFields(this); - } - - toJSON(): any { - return (this.constructor as any).toJSON(this); - } - - toConstant(): this { - return (this.constructor as any).toConstant(this); - } - - equals(x: this) { - return Provable.equal(this, x); - } - - assertEquals(x: this) { - Provable.assertEqual(this, x); - } - - isConstant() { - return this.toFields().every((x) => x.isConstant()); - } - - static fromFields( - this: T, - xs: Field[] - ): InstanceType { - const fields: [string, any][] = (this as any).prototype._fields; - if (xs.length < fields.length) { - throw Error( - `${this.name}.fromFields: Expected ${fields.length} field elements, got ${xs?.length}` - ); - } - let offset = 0; - const props: any = {}; - for (let i = 0; i < fields.length; ++i) { - const [key, propType] = fields[i]; - const propSize = propType.sizeInFields(); - const propVal = propType.fromFields( - xs.slice(offset, offset + propSize), - [] - ); - props[key] = propVal; - offset += propSize; - } - return Object.assign(Object.create(this.prototype), props); - } - - static check(this: T, v: InstanceType) { - const fields = (this as any).prototype._fields; - if (fields === undefined || fields === null) { - return; - } - for (let i = 0; i < fields.length; ++i) { - const [key, propType] = fields[i]; - const value = (v as any)[key]; - if (propType.check === undefined) - throw Error('bug: CircuitValue without .check()'); - propType.check(value); - } - } - - static toConstant( - this: T, - t: InstanceType - ): InstanceType { - const xs: Field[] = (this as any).toFields(t); - return (this as any).fromFields(xs.map((x) => x.toConstant())); - } - - static toJSON(this: T, v: InstanceType) { - const res: any = {}; - if ((this as any).prototype._fields !== undefined) { - const fields: [string, any][] = (this as any).prototype._fields; - fields.forEach(([key, propType]) => { - res[key] = propType.toJSON((v as any)[key]); - }); - } - return res; - } - - static fromJSON( - this: T, - value: any - ): InstanceType { - let props: any = {}; - let fields: [string, any][] = (this as any).prototype._fields; - if (typeof value !== 'object' || value === null || Array.isArray(value)) { - throw Error(`${this.name}.fromJSON(): invalid input ${value}`); - } - if (fields !== undefined) { - for (let i = 0; i < fields.length; ++i) { - let [key, propType] = fields[i]; - if (value[key] === undefined) { - throw Error(`${this.name}.fromJSON(): invalid input ${value}`); - } else { - props[key] = propType.fromJSON(value[key]); - } - } - } - return Object.assign(Object.create(this.prototype), props); - } -} - -function prop(this: any, target: any, key: string) { - const fieldType = Reflect.getMetadata('design:type', target, key); - if (!target.hasOwnProperty('_fields')) { - target._fields = []; - } - if (fieldType === undefined) { - } else if (fieldType.toFields && fieldType.fromFields) { - target._fields.push([key, fieldType]); - } else { - console.log( - `warning: property ${key} missing field element conversion methods` - ); - } -} - -function arrayProp(elementType: FlexibleProvable, length: number) { - return function (target: any, key: string) { - if (!target.hasOwnProperty('_fields')) { - target._fields = []; - } - target._fields.push([key, Provable.Array(elementType, length)]); - }; -} - -function matrixProp( - elementType: FlexibleProvable, - nRows: number, - nColumns: number -) { - return function (target: any, key: string) { - if (!target.hasOwnProperty('_fields')) { - target._fields = []; - } - target._fields.push([ - key, - Provable.Array(Provable.Array(elementType, nColumns), nRows), - ]); - }; -} - -/** - * `Struct` lets you declare composite types for use in snarkyjs circuits. + * `Struct` lets you declare composite types for use in o1js circuits. * * These composite types can be passed in as arguments to smart contract methods, used for on-chain state variables * or as event / action types. @@ -337,7 +114,7 @@ function matrixProp( * ``` * * In addition to creating types composed of Field elements, you can also include auxiliary data which does not become part of the proof. - * This, for example, allows you to re-use the same type outside snarkyjs methods, where you might want to store additional metadata. + * This, for example, allows you to re-use the same type outside o1js methods, where you might want to store additional metadata. * * To declare non-proof values of type `string`, `number`, etc, you can use the built-in objects `String`, `Number`, etc. * Here's how we could add the voter's name (a string) as auxiliary data: @@ -362,8 +139,7 @@ function Struct< J extends InferJson = InferJson, Pure extends boolean = IsPure >( - type: A, - options: { customObjectKeys?: string[] } = {} + type: A ): (new (value: T) => T) & { _isStruct: true } & (Pure extends true ? ProvablePure : Provable) & { @@ -373,9 +149,10 @@ function Struct< }; toJSON: (x: T) => J; fromJSON: (x: J) => T; + empty: () => T; } { class Struct_ { - static type = provable(type, options); + static type = provable(type); static _isStruct: true; constructor(value: T) { @@ -430,6 +207,15 @@ function Struct< let struct = Object.create(this.prototype); return Object.assign(struct, value); } + /** + * Create an instance of this struct filled with default values + * @returns an empty instance of this struct + */ + static empty(): T { + let value = this.type.empty(); + let struct = Object.create(this.prototype); + return Object.assign(struct, value); + } /** * This method is for internal use, you will probably not need it. * Method to make assertions which should be always made whenever a struct of this type is created in a proof. @@ -453,6 +239,24 @@ function Struct< return Struct_ as any; } +function StructNoJson< + A, + T extends InferProvable = InferProvable, + Pure extends boolean = IsPure +>( + type: A +): (new (value: T) => T) & { _isStruct: true } & (Pure extends true + ? ProvablePure + : Provable) & { + toInput: (x: T) => { + fields?: Field[] | undefined; + packed?: [Field, number][] | undefined; + }; + empty: () => T; + } { + return Struct(type) satisfies Provable as any; +} + let primitives = new Set([Field, Bool, Scalar, Group]); function isPrimitive(obj: any) { for (let P of primitives) { @@ -465,14 +269,8 @@ function cloneCircuitValue(obj: T): T { // primitive JS types and functions aren't cloned if (typeof obj !== 'object' || obj === null) return obj; - // HACK: callbacks, account udpates - if ( - obj.constructor?.name.includes('GenericArgument') || - obj.constructor?.name.includes('Callback') - ) { - return obj; - } - if (obj.constructor?.name.includes('AccountUpdate')) { + // classes that define clone() are cloned using that method + if (obj.constructor !== undefined && 'clone' in obj.constructor) { return (obj as any).constructor.clone(obj); } @@ -486,10 +284,13 @@ function cloneCircuitValue(obj: T): T { ) as any as T; if (ArrayBuffer.isView(obj)) return new (obj.constructor as any)(obj); - // snarkyjs primitives aren't cloned + // o1js primitives and proofs aren't cloned if (isPrimitive(obj)) { return obj; } + if (obj instanceof Proof) { + return obj; + } // cloning strategy that works for plain objects AND classes whose constructor only assigns properties let propertyDescriptors: Record = {}; @@ -543,7 +344,7 @@ function circuitValueEquals(a: T, b: T): boolean { ); } - // the two checks below cover snarkyjs primitives and CircuitValues + // the two checks below cover o1js primitives and CircuitValues // if we have an .equals method, try to use it if ('equals' in a && typeof (a as any).equals === 'function') { let isEqual = (a as any).equals(b).toBoolean(); @@ -570,16 +371,3 @@ function circuitValueEquals(a: T, b: T): boolean { ([key, value]) => key in b && circuitValueEquals((b as any)[key], value) ); } - -function toConstant(type: FlexibleProvable, value: T): T; -function toConstant(type: Provable, value: T): T { - return type.fromFields( - type.toFields(value).map((x) => x.toConstant()), - type.toAuxiliary(value) - ); -} - -function isConstant(type: FlexibleProvable, value: T): boolean; -function isConstant(type: Provable, value: T): boolean { - return type.toFields(value).every((x) => x.isConstant()); -} diff --git a/src/lib/provable/types/unconstrained.ts b/src/lib/provable/types/unconstrained.ts new file mode 100644 index 0000000000..a29531a1a6 --- /dev/null +++ b/src/lib/provable/types/unconstrained.ts @@ -0,0 +1,126 @@ +import { Snarky } from '../../../snarky.js'; +import type { Field } from '../field.js'; +import type { Provable } from '../provable.js'; +import { assert } from '../../util/errors.js'; +import { asProver, inCheckedComputation } from '../core/provable-context.js'; +import { witness } from './witness.js'; + +export { Unconstrained }; + +/** + * Container which holds an unconstrained value. This can be used to pass values + * between the out-of-circuit blocks in provable code. + * + * Invariants: + * - An `Unconstrained`'s value can only be accessed in auxiliary contexts. + * - An `Unconstrained` can be empty when compiling, but never empty when running as the prover. + * (there is no way to create an empty `Unconstrained` in the prover) + * + * @example + * ```ts + * let x = Unconstrained.from(0n); + * + * class MyContract extends SmartContract { + * `@method` myMethod(x: Unconstrained) { + * + * Provable.witness(Field, () => { + * // we can access and modify `x` here + * let newValue = x.get() + otherField.toBigInt(); + * x.set(newValue); + * + * // ... + * }); + * + * // throws an error! + * x.get(); + * } + * ``` + */ +class Unconstrained { + private option: + | { isSome: true; value: T } + | { isSome: false; value: undefined }; + + private constructor(isSome: boolean, value?: T) { + this.option = { isSome, value: value as any }; + } + + /** + * Read an unconstrained value. + * + * Note: Can only be called outside provable code. + */ + get(): T { + if (inCheckedComputation() && !Snarky.run.inProverBlock()) + throw Error(`You cannot use Unconstrained.get() in provable code. + +The only place where you can read unconstrained values is in Provable.witness() +and Provable.asProver() blocks, which execute outside the proof. +`); + assert(this.option.isSome, 'Empty `Unconstrained`'); // never triggered + return this.option.value; + } + + /** + * Modify the unconstrained value. + */ + set(value: T) { + this.option = { isSome: true, value }; + } + + /** + * Set the unconstrained value to the same as another `Unconstrained`. + */ + setTo(value: Unconstrained) { + this.option = value.option; + } + + /** + * Create an `Unconstrained` with the given `value`. + * + * Note: If `T` contains provable types, `Unconstrained.from` is an anti-pattern, + * because it stores witnesses in a space that's intended to be used outside the proof. + * Something like the following should be used instead: + * + * ```ts + * let xWrapped = Unconstrained.witness(() => Provable.toConstant(type, x)); + * ``` + */ + static from(value: T) { + return new Unconstrained(true, value); + } + + /** + * Create an `Unconstrained` from a witness computation. + */ + static witness(compute: () => T) { + return witness( + Unconstrained.provable, + () => new Unconstrained(true, compute()) + ); + } + + /** + * Update an `Unconstrained` by a witness computation. + */ + updateAsProver(compute: (value: T) => T) { + return asProver(() => { + let value = this.get(); + this.set(compute(value)); + }); + } + + static provable: Provable> & { + toInput: (x: Unconstrained) => { + fields?: Field[]; + packed?: [Field, number][]; + }; + } = { + sizeInFields: () => 0, + toFields: () => [], + toAuxiliary: (t?: any) => [t ?? new Unconstrained(false)], + fromFields: (_, [t]) => t, + check: () => {}, + toInput: () => ({}), + }; +} diff --git a/src/lib/provable/types/witness.ts b/src/lib/provable/types/witness.ts new file mode 100644 index 0000000000..c432eac1b3 --- /dev/null +++ b/src/lib/provable/types/witness.ts @@ -0,0 +1,86 @@ +import type { Field } from '../field.js'; +import type { FlexibleProvable } from './struct.js'; +import type { Provable } from './provable-intf.js'; +import { + inCheckedComputation, + snarkContext, +} from '../core/provable-context.js'; +import { exists, existsAsync } from '../core/exists.js'; + +export { witness, witnessAsync }; + +function witness = FlexibleProvable>( + type: S, + compute: () => T +): T { + let ctx = snarkContext.get(); + + // outside provable code, we just call the callback and return its cloned result + if (!inCheckedComputation() || ctx.inWitnessBlock) { + return clone(type, compute()); + } + let proverValue: T | undefined = undefined; + let fields: Field[]; + + let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); + try { + fields = exists(type.sizeInFields(), () => { + proverValue = compute(); + let fields = type.toFields(proverValue); + return fields.map((x) => x.toBigInt()); + }); + } finally { + snarkContext.leave(id); + } + + // rebuild the value from its fields (which are now variables) and aux data + let aux = type.toAuxiliary(proverValue); + let value = (type as Provable).fromFields(fields, aux); + + // add type-specific constraints + type.check(value); + + return value; +} + +async function witnessAsync< + T, + S extends FlexibleProvable = FlexibleProvable +>(type: S, compute: () => Promise): Promise { + let ctx = snarkContext.get(); + + // outside provable code, we just call the callback and return its cloned result + if (!inCheckedComputation() || ctx.inWitnessBlock) { + let value: T = await compute(); + return clone(type, value); + } + let proverValue: T | undefined = undefined; + let fields: Field[]; + + // call into `existsAsync` to witness the raw field elements + let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); + try { + fields = await existsAsync(type.sizeInFields(), async () => { + proverValue = await compute(); + let fields = type.toFields(proverValue); + return fields.map((x) => x.toBigInt()); + }); + } finally { + snarkContext.leave(id); + } + + // rebuild the value from its fields (which are now variables) and aux data + let aux = type.toAuxiliary(proverValue); + let value = (type as Provable).fromFields(fields, aux); + + // add type-specific constraints + type.check(value); + + return value; +} + +function clone>(type: S, value: T): T { + let fields = type.toFields(value); + let aux = type.toAuxiliary?.(value) ?? []; + return (type as Provable).fromFields(fields, aux); +} diff --git a/src/lib/provable/wrapped-classes.ts b/src/lib/provable/wrapped-classes.ts new file mode 100644 index 0000000000..ad11e446b0 --- /dev/null +++ b/src/lib/provable/wrapped-classes.ts @@ -0,0 +1,21 @@ +import { Bytes as InternalBytes, createBytes } from './bytes.js'; + +export { Bytes }; + +type Bytes = InternalBytes; + +/** + * A provable type representing an array of bytes. + * + * ```ts + * class Bytes32 extends Bytes(32) {} + * + * let bytes = Bytes32.fromHex('deadbeef'); + * ``` + */ +function Bytes(size: number) { + return createBytes(size); +} +Bytes.from = InternalBytes.from; +Bytes.fromHex = InternalBytes.fromHex; +Bytes.fromString = InternalBytes.fromString; diff --git a/src/lib/core.ts b/src/lib/provable/wrapped.ts similarity index 100% rename from src/lib/core.ts rename to src/lib/provable/wrapped.ts diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts new file mode 100644 index 0000000000..4189ffa6bf --- /dev/null +++ b/src/lib/testing/constraint-system.ts @@ -0,0 +1,453 @@ +/** + * DSL for testing that a gadget generates the expected constraint system. + * + * An essential feature is that `constraintSystem()` automatically generates a + * variety of fieldvar types for the inputs: constants, variables, and combinators. + */ +import { Gate, GateType } from '../../snarky.js'; +import { randomBytes } from '../../bindings/crypto/random.js'; +import { Field } from '../provable/field.js'; +import { FieldType, FieldVar } from '../provable/core/fieldvar.js'; +import { Provable } from '../provable/provable.js'; +import { Tuple } from '../util/types.js'; +import { Random } from './random.js'; +import { test } from './property.js'; +import { Undefined, ZkProgram } from '../proof-system/zkprogram.js'; +import { + constraintSystemSync, + printGates, +} from '../provable/core/provable-context.js'; + +export { + constraintSystem, + not, + and, + or, + fulfills, + equals, + contains, + allConstant, + ifNotAllConstant, + isEmpty, + withoutGenerics, + print, + repeat, + ConstraintSystemTest, +}; + +/** + * `constraintSystem()` is a test runner to check properties of constraint systems. + * You give it a description of inputs and a circuit, as well as a `ConstraintSystemTest` to assert + * properties on the generated constraint system. + * + * As input variables, we generate random combinations of constants, variables, add & scale combinators, + * to poke for the common problem of gate chains broken by unexpected Generic gates. + * + * The `constraintSystemTest` is written using a DSL of property assertions, such as {@link equals} and {@link contains}. + * To run multiple assertions, use the {@link and} / {@link or} combinators. + * To debug the constraint system, use the {@link print} test or `and(print, ...otherTests)`. + * + * @param label description of the constraint system + * @param inputs input spec in form `{ from: [...provables] }` + * @param main circuit to test + * @param constraintSystemTest property test to run on the constraint system + */ +function constraintSystem>>( + label: string, + inputs: { from: Input }, + main: (...args: CsParams) => void, + constraintSystemTest: ConstraintSystemTest +) { + // create random generators + let types = inputs.from.map(provable); + let rngs = types.map(layout); + + test(...rngs, (...args) => { + let layouts = args.slice(0, -1); + + // compute the constraint system + let { gates } = constraintSystemSync(() => { + // each random input "layout" has to be instantiated into vars in this circuit + let values = types.map((type, i) => + instantiate(type, layouts[i]) + ) as CsParams; + main(...values); + }); + + // run tests + let typesAndValues = types.map((type, i) => ({ type, value: layouts[i] })); + + let { ok, failures } = run(constraintSystemTest, gates, typesAndValues); + + if (!ok) { + console.log('Constraint system:'); + printGates(gates); + + throw Error( + `Constraint system test: ${label}\n\n${failures + .map((f) => `FAIL: ${f}`) + .join('\n')}\n` + ); + } + }); +} + +/** + * Convenience function to run {@link constraintSystem} on the method of a {@link ZkProgram}. + * + * @example + * ```ts + * const program = ZkProgram({ methods: { myMethod: ... }, ... }); + * + * constraintSystem.fromZkProgram(program, 'myMethod', contains('Rot64')); + * ``` + */ +constraintSystem.fromZkProgram = function fromZkProgram< + T, + K extends keyof T & string +>( + program: { privateInputTypes: T }, + methodName: K, + test: ConstraintSystemTest +) { + let program_: ZkProgram = program as any; + let from: any = [...program_.privateInputTypes[methodName]]; + if (program_.publicInputType !== Undefined) { + from.unshift(program_.publicInputType); + } + return constraintSystem( + `${program_.name} / ${methodName}()`, + { from }, + program_.rawMethods[methodName], + test + ); +}; + +// DSL for writing tests + +type ConstraintSystemTestBase = { + run: (cs: Gate[], inputs: TypeAndValue[]) => boolean; + label: string; +}; +type Base = { kind?: undefined } & ConstraintSystemTestBase; +type Not = { kind: 'not' } & ConstraintSystemTestBase; +type And = { kind: 'and'; tests: ConstraintSystemTest[]; label: string }; +type Or = { kind: 'or'; tests: ConstraintSystemTest[]; label: string }; +type ConstraintSystemTest = Base | Not | And | Or; + +type Result = { ok: boolean; failures: string[] }; + +function run( + test: ConstraintSystemTest, + cs: Gate[], + inputs: TypeAndValue[] +): Result { + switch (test.kind) { + case undefined: { + let ok = test.run(cs, inputs); + let failures = ok ? [] : [test.label]; + return { ok, failures }; + } + case 'not': { + let ok = test.run(cs, inputs); + let failures = ok ? [`not(${test.label})`] : []; + return { ok: !ok, failures }; + } + case 'and': { + let results = test.tests.map((t) => run(t, cs, inputs)); + let ok = results.every((r) => r.ok); + let failures = ok ? [] : results.flatMap((r) => r.failures); + return { ok, failures }; + } + case 'or': { + let results = test.tests.map((t) => run(t, cs, inputs)); + let ok = results.some((r) => r.ok); + let failures = ok ? [] : results.flatMap((r) => r.failures); + return { ok, failures }; + } + } +} + +/** + * Negate a test. + */ +function not(test: ConstraintSystemTest): ConstraintSystemTest { + return { kind: 'not', ...test }; +} +/** + * Check that all input tests pass. + */ +function and(...tests: ConstraintSystemTest[]): ConstraintSystemTest { + return { kind: 'and', tests, label: `and(${tests.map((t) => t.label)})` }; +} +/** + * Check that at least one input test passes. + */ +function or(...tests: ConstraintSystemTest[]): ConstraintSystemTest { + return { kind: 'or', tests, label: `or(${tests.map((t) => t.label)})` }; +} + +/** + * General test + */ +function fulfills( + label: string, + run: (cs: Gate[], inputs: TypeAndValue[]) => boolean +): ConstraintSystemTest { + return { run, label }; +} + +/** + * Test for precise equality of the constraint system with a given list of gates. + */ +function equals(gates: readonly GateType[]): ConstraintSystemTest { + return { + run(cs) { + if (cs.length !== gates.length) return false; + return cs.every((g, i) => g.type === gates[i]); + }, + label: `equals ${JSON.stringify(gates)}`, + }; +} + +/** + * Test that constraint system contains each of a list of gates consecutively. + * + * You can also pass a list of lists. In that case, the constraint system has to contain + * each of the lists of gates in the given order, but not necessarily consecutively. + * + * @example + * ```ts + * // constraint system contains a Rot64 gate + * contains('Rot64') + * + * // constraint system contains a Rot64 gate, followed directly by a RangeCheck0 gate + * contains(['Rot64', 'RangeCheck0']) + * + * // constraint system contains two instances of the combination [Rot64, RangeCheck0] + * contains([['Rot64', 'RangeCheck0'], ['Rot64', 'RangeCheck0']]]) + * ``` + */ +function contains( + gates: GateType | readonly GateType[] | readonly GateType[][] +): ConstraintSystemTest { + let expectedGatess = toGatess(gates); + return { + run(cs) { + let gates = cs.map((g) => g.type); + let i = 0; + let j = 0; + for (let gate of gates) { + if (gate === expectedGatess[i][j]) { + j++; + if (j === expectedGatess[i].length) { + i++; + j = 0; + if (i === expectedGatess.length) return true; + } + } else if (gate === expectedGatess[i][0]) { + j = 1; + } else { + j = 0; + } + } + return false; + }, + label: `contains ${JSON.stringify(expectedGatess)}`, + }; +} + +/** + * Test whether all inputs are constant. + */ +const allConstant: ConstraintSystemTest = { + run(cs, inputs) { + return inputs.every(({ type, value }) => + type.toFields(value).every((x) => x.isConstant()) + ); + }, + label: 'all inputs constant', +}; + +/** + * Modifies a test so that it doesn't fail if all inputs are constant, and instead + * checks that the constraint system is empty in that case. + */ +function ifNotAllConstant(test: ConstraintSystemTest): ConstraintSystemTest { + return or(test, and(allConstant, isEmpty)); +} + +/** + * Test whether constraint system is empty. + */ +const isEmpty = fulfills('constraint system is empty', (cs) => cs.length === 0); + +/** + * Modifies a test so that it runs on the constraint system with generic gates filtered out. + */ +function withoutGenerics(test: ConstraintSystemTest): ConstraintSystemTest { + return { + run(cs, inputs) { + return run( + test, + cs.filter((g) => g.type !== 'Generic'), + inputs + ).ok; + }, + label: `withoutGenerics(${test.label})`, + }; +} + +/** + * "Test" that just pretty-prints the constraint system. + */ +const print: ConstraintSystemTest = { + run(cs) { + console.log('Constraint system:'); + printGates(cs); + return true; + }, + label: '', +}; + +// Do other useful things with constraint systems + +/** + * Get constraint system as a list of gates. + */ +constraintSystem.gates = function gates>>( + inputs: { from: Input }, + main: (...args: CsParams) => void +) { + let types = inputs.from.map(provable); + let { gates } = constraintSystemSync(() => { + let values = types.map((type) => + Provable.witness(type, (): unknown => { + throw Error('not needed'); + }) + ) as CsParams; + main(...values); + }); + return gates; +}; + +function map(transform: (gates: Gate[]) => T) { + return >>( + inputs: { from: Input }, + main: (...args: CsParams) => void + ) => transform(constraintSystem.gates(inputs, main)); +} + +/** + * Get size of constraint system. + */ +constraintSystem.size = map((gates) => gates.length); + +/** + * Print constraint system. + */ +constraintSystem.print = map(printGates); + +function repeat( + n: number, + gates: GateType | readonly GateType[] +): readonly GateType[] { + gates = Array.isArray(gates) ? gates : [gates]; + return Array(n).fill(gates).flat(); +} + +function toGatess( + gateTypes: GateType | readonly GateType[] | readonly GateType[][] +): GateType[][] { + if (typeof gateTypes === 'string') return [[gateTypes]]; + if (Array.isArray(gateTypes[0])) return gateTypes as GateType[][]; + return [gateTypes as GateType[]]; +} + +// Random generator for arbitrary provable types + +function provable(spec: CsVarSpec): Provable { + return 'provable' in spec ? spec.provable : spec; +} + +function layout(type: Provable): Random { + let length = type.sizeInFields(); + + return Random(() => { + let fields = Array.from({ length }, () => new Field(drawFieldVar())); + return type.fromFields(fields, type.toAuxiliary()); + }); +} + +function instantiate(type: Provable, value: T) { + let fields = type.toFields(value).map((x) => instantiateFieldVar(x.value)); + return type.fromFields(fields, type.toAuxiliary()); +} + +// Random generator for fieldvars that exercises constants, variables and combinators + +function drawFieldVar(): FieldVar { + let fieldType = drawFieldType(); + switch (fieldType) { + case FieldType.Constant: { + return FieldVar.constant(1n); + } + case FieldType.Var: { + return [FieldType.Var, 0]; + } + case FieldType.Add: { + let x = drawFieldVar(); + let y = drawFieldVar(); + // prevent blow-up of constant size + if (x[0] === FieldType.Constant && y[0] === FieldType.Constant) return x; + return FieldVar.add(x, y); + } + case FieldType.Scale: { + let x = drawFieldVar(); + // prevent blow-up of constant size + if (x[0] === FieldType.Constant) return x; + return FieldVar.scale(3n, x); + } + } +} + +function instantiateFieldVar(x: FieldVar): Field { + switch (x[0]) { + case FieldType.Constant: { + return new Field(x); + } + case FieldType.Var: { + return Provable.witness(Field, () => Field.from(0n)); + } + case FieldType.Add: { + let a = instantiateFieldVar(x[1]); + let b = instantiateFieldVar(x[2]); + return a.add(b); + } + case FieldType.Scale: { + let a = instantiateFieldVar(x[2]); + return a.mul(x[1][1]); + } + } +} + +function drawFieldType(): FieldType { + let oneOf8 = randomBytes(1)[0] & 0b111; + if (oneOf8 < 4) return FieldType.Var; + if (oneOf8 < 6) return FieldType.Constant; + if (oneOf8 === 6) return FieldType.Scale; + return FieldType.Add; +} + +// types + +type CsVarSpec = Provable | { provable: Provable }; +type InferCsVar = T extends { provable: Provable } + ? U + : T extends Provable + ? U + : never; +type CsParams>> = { + [k in keyof In]: InferCsVar; +}; +type TypeAndValue = { type: Provable; value: T }; diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts new file mode 100644 index 0000000000..b5a8406cee --- /dev/null +++ b/src/lib/testing/equivalent.ts @@ -0,0 +1,508 @@ +/** + * helpers for testing equivalence of two implementations, one of them on bigints + */ +import { test, Random } from '../testing/property.js'; +import { Provable } from '../provable/provable.js'; +import { deepEqual } from 'node:assert/strict'; +import { Bool, Field } from '../provable/wrapped.js'; +import { AnyFunction, Tuple } from '../util/types.js'; +import { provable } from '../provable/types/struct.js'; +import { assert } from '../provable/gadgets/common.js'; +import { runAndCheckSync } from '../provable/core/provable-context.js'; + +export { + equivalent, + equivalentProvable, + equivalentAsync, + oneOf, + throwError, + handleErrors, + deepEqual as defaultAssertEqual, + id, +}; +export { + spec, + field, + fieldWithRng, + bigintField, + bool, + boolean, + unit, + array, + record, + map, + onlyIf, + fromRandom, + first, + second, + constant, +}; +export { + Spec, + ToSpec, + FromSpec, + SpecFromFunctions, + ProvableSpec, + First, + Second, +}; + +// a `Spec` tells us how to compare two functions + +type FromSpec = { + // `rng` creates random inputs to the first function + rng: Random; + + // `there` converts to inputs to the second function + there: (x: In1) => In2; + + // `provable` tells us how to create witnesses, to test provable code + // note: we only allow the second function to be provable; + // the second because it's more natural to have non-provable types as random generator output + provable?: Provable; +}; + +type ToSpec = { + // `back` converts outputs of the second function back to match the first function + back: (x: Out2) => Out1; + + // `assertEqual` to compare outputs against each other; defaults to `deepEqual` + assertEqual?: (x: Out1, y: Out1, message: string) => void; +}; + +type Spec = FromSpec & ToSpec; + +type ProvableSpec = Spec & { provable: Provable }; + +type FuncSpec, Out1, In2 extends Tuple, Out2> = { + from: { + [k in keyof In1]: k extends keyof In2 ? FromSpec : never; + }; + to: ToSpec; +}; + +type SpecFromFunctions< + F1 extends AnyFunction, + F2 extends AnyFunction +> = FuncSpec, ReturnType, Parameters, ReturnType>; + +function id(x: T) { + return x; +} + +// unions of specs, to cleanly model function parameters that are unions of types + +type FromSpecUnion = { + _isUnion: true; + specs: Tuple>; + rng: Random<[number, T1]>; +}; + +type OrUnion = FromSpec | FromSpecUnion; + +type Union = T[keyof T & number]; + +function oneOf>>( + ...specs: In +): FromSpecUnion>, Union>> { + // the randomly generated value from a union keeps track of which spec it came from + let rng = Random.oneOf( + ...specs.map((spec, i) => + Random.map(spec.rng, (x) => [i, x] as [number, any]) + ) + ); + return { _isUnion: true, specs, rng }; +} + +function toUnion(spec: OrUnion): FromSpecUnion { + let specAny = spec as any; + return specAny._isUnion ? specAny : oneOf(specAny); +} + +// equivalence tester + +function equivalent< + In extends Tuple>, + Out extends ToSpec +>({ from, to, verbose }: { from: In; to: Out; verbose?: boolean }) { + return function run( + f1: (...args: Params1) => First, + f2: (...args: Params2) => Second, + label = 'expect equal results' + ) { + let generators = from.map((spec) => spec.rng); + let assertEqual = to.assertEqual ?? deepEqual; + let start = performance.now(); + let nRuns = test(...(generators as any[]), (...args) => { + args.pop(); + let inputs = args as Params1; + handleErrors( + () => f1(...inputs), + () => + to.back( + f2(...(inputs.map((x, i) => from[i].there(x)) as Params2)) + ), + (x, y) => assertEqual(x, y, label), + label + ); + }); + + if (verbose) { + let ms = (performance.now() - start).toFixed(1); + let runs = nRuns.toString().padStart(2, ' '); + console.log( + `${label.padEnd(20, ' ')} success on ${runs} runs in ${ms}ms.` + ); + } + }; +} + +// async equivalence + +function equivalentAsync< + In extends Tuple>, + Out extends ToSpec +>({ from, to }: { from: In; to: Out }, { runs = 1 } = {}) { + return async function run( + f1: (...args: Params1) => Promise> | First, + f2: (...args: Params2) => Promise> | Second, + label = 'expect equal results' + ) { + let generators = from.map((spec) => spec.rng); + let assertEqual = to.assertEqual ?? deepEqual; + + let nexts = generators.map((g) => g.create()); + + for (let i = 0; i < runs; i++) { + let args = nexts.map((next) => next()); + let inputs = args as Params1; + try { + await handleErrorsAsync( + () => f1(...inputs), + async () => + to.back( + await f2( + ...(inputs.map((x, i) => from[i].there(x)) as Params2) + ) + ), + (x, y) => assertEqual(x, y, label), + label + ); + } catch (err) { + console.log(...inputs); + throw err; + } + } + }; +} + +// equivalence tester for provable code + +function isProvable(spec: FromSpecUnion) { + return spec.specs.some((spec) => spec.provable); +} + +function equivalentProvable< + In extends Tuple>, + Out extends ToSpec +>({ from: fromRaw, to, verbose }: { from: In; to: Out; verbose?: boolean }) { + let fromUnions = fromRaw.map(toUnion); + assert(fromUnions.some(isProvable), 'equivalentProvable: no provable input'); + + return function run( + f1: (...args: Params1) => First, + f2: (...args: Params2) => Second, + label = 'expect equal results' + ) { + let generators = fromUnions.map((spec) => spec.rng); + let assertEqual = to.assertEqual ?? deepEqual; + + let start = performance.now(); + let nRuns = test.custom({ minRuns: 5 })(...generators, (...args) => { + args.pop(); + + // figure out which spec to use for each argument + let from = (args as [number, unknown][]).map( + ([j], i) => fromUnions[i].specs[j] + ); + let inputs = (args as [number, unknown][]).map( + ([, x]) => x + ) as Params1; + let inputs2 = inputs.map((x, i) => from[i].there(x)) as Params2; + + // outside provable code + handleErrors( + () => f1(...inputs), + () => f2(...inputs2), + (x, y) => assertEqual(x, to.back(y), label), + label + ); + + // inside provable code + runAndCheckSync(() => { + let inputWitnesses = inputs2.map((x, i) => { + let provable = from[i].provable; + return provable !== undefined + ? Provable.witness(provable, () => x) + : x; + }) as Params2; + handleErrors( + () => f1(...inputs), + () => f2(...inputWitnesses), + (x, y) => Provable.asProver(() => assertEqual(x, to.back(y), label)), + label + ); + }); + }); + if (verbose) { + let ms = (performance.now() - start).toFixed(1); + let runs = nRuns.toString().padStart(2, ' '); + console.log( + `${label.padEnd(20, ' ')} success on ${runs} runs in ${ms}ms.` + ); + } + }; +} + +// creating specs + +function spec(spec: { + rng: Random; + there: (x: T) => S; + back: (x: S) => T; + assertEqual?: (x: T, y: T, message: string) => void; + provable: Provable; +}): ProvableSpec; +function spec(spec: { + rng: Random; + there: (x: T) => S; + back: (x: S) => T; + assertEqual?: (x: T, y: T, message: string) => void; +}): Spec; +function spec(spec: { + rng: Random; + assertEqual?: (x: T, y: T, message: string) => void; +}): Spec; +function spec(spec: { + rng: Random; + there?: (x: T) => S; + back?: (x: S) => T; + assertEqual?: (x: T, y: T, message: string) => void; + provable?: Provable; +}): Spec { + return { + rng: spec.rng, + there: spec.there ?? (id as any), + back: spec.back ?? (id as any), + assertEqual: spec.assertEqual, + provable: spec.provable, + }; +} + +// some useful specs + +let unit: ToSpec = { back: id, assertEqual() {} }; + +let field: ProvableSpec = { + rng: Random.field, + there: Field, + back: (x) => x.toBigInt(), + provable: Field, +}; + +let bigintField: Spec = { + rng: Random.field, + there: id, + back: id, +}; + +let bool: ProvableSpec = { + rng: Random.boolean, + there: Bool, + back: (x) => x.toBoolean(), + provable: Bool, +}; +let boolean: Spec = fromRandom(Random.boolean); + +function fieldWithRng(rng: Random): Spec { + return { ...field, rng }; +} + +// spec combinators + +function array( + spec: ProvableSpec, + n: number +): ProvableSpec; +function array( + spec: Spec, + n: Random | number +): Spec; +function array( + spec: Spec, + n: Random | number +): Spec { + return { + rng: Random.array(spec.rng, n), + there: (x) => x.map(spec.there), + back: (x) => x.map(spec.back), + provable: + typeof n === 'number' && spec.provable + ? Provable.Array(spec.provable, n) + : undefined, + }; +} + +function record }>( + specs: Specs +): Spec< + { [k in keyof Specs]: First }, + { [k in keyof Specs]: Second } +> { + let isProvable = Object.values(specs).every((spec) => spec.provable); + return { + rng: Random.record(mapObject(specs, (spec) => spec.rng)) as any, + there: (x) => mapObject(specs, (spec, k) => spec.there(x[k])) as any, + back: (x) => mapObject(specs, (spec, k) => spec.back(x[k])) as any, + provable: isProvable + ? provable(mapObject(specs, (spec) => spec.provable) as any) + : undefined, + }; +} + +function map( + { from, to }: { from: FromSpec; to: Spec }, + there: (t: T1) => S1 +): Spec { + return { ...to, rng: Random.map(from.rng, there) }; +} + +function onlyIf(spec: Spec, onlyIf: (t: T) => boolean): Spec { + return { ...spec, rng: Random.reject(spec.rng, (x) => !onlyIf(x)) }; +} + +function mapObject( + t: { [k in K]: T }, + map: (t: T, k: K) => S +): { [k in K]: S } { + return Object.fromEntries( + Object.entries(t).map(([k, v]) => [k, map(v, k as K)]) + ) as any; +} + +function fromRandom(rng: Random): Spec { + return { rng, there: id, back: id }; +} + +function first(spec: Spec): Spec { + return { rng: spec.rng, there: id, back: id }; +} +function second(spec: Spec): Spec { + return { + rng: Random.map(spec.rng, spec.there), + there: id, + back: id, + provable: spec.provable, + }; +} + +function constant(spec: Spec, value: T): Spec { + return { ...spec, rng: Random.constant(value) }; +} + +// helper to ensure two functions throw equivalent errors + +function handleErrors( + op1: () => T, + op2: () => S, + useResults?: (a: T, b: S) => R, + label?: string +): R | undefined { + let result1: T, result2: S; + let error1: Error | undefined; + let error2: Error | undefined; + try { + result1 = op1(); + } catch (err) { + error1 = err as Error; + } + try { + result2 = op2(); + } catch (err) { + error2 = err as Error; + } + if (!!error1 !== !!error2) { + error1 && console.log(error1); + error2 && console.log(error2); + } + let message = `${(label && `${label}: `) || ''}equivalent errors`; + deepEqual(!!error1, !!error2, message); + if (!(error1 || error2) && useResults !== undefined) { + return useResults(result1!, result2!); + } +} + +async function handleErrorsAsync( + op1: () => T, + op2: () => S, + useResults?: (a: Awaited, b: Awaited) => R, + label?: string +): Promise { + let result1: Awaited, result2: Awaited; + let error1: Error | undefined; + let error2: Error | undefined; + try { + result1 = await op1(); + } catch (err) { + error1 = err as Error; + } + try { + result2 = await op2(); + } catch (err) { + error2 = err as Error; + } + if (!!error1 !== !!error2) { + error1 && console.log(error1); + error2 && console.log(error2); + } + let message = `${(label && `${label}: `) || ''}equivalent errors`; + deepEqual(!!error1, !!error2, message); + if (!(error1 || error2) && useResults !== undefined) { + return useResults(result1!, result2!); + } +} + +function throwError(message?: string): any { + throw Error(message); +} + +// infer input types from specs + +type Param1> = In extends { + there: (x: infer In) => any; +} + ? In + : In extends FromSpecUnion + ? T1 + : never; +type Param2> = In extends { + there: (x: any) => infer In; +} + ? In + : In extends FromSpecUnion + ? T2 + : never; + +type Params1>> = { + [k in keyof Ins]: Param1; +}; +type Params2>> = { + [k in keyof Ins]: Param2; +}; + +type First> = Out extends ToSpec + ? Out1 + : never; +type Second> = Out extends ToSpec + ? Out2 + : never; diff --git a/src/lib/testing/random.ts b/src/lib/testing/random.ts index c23d52589a..e4fa1818c5 100644 --- a/src/lib/testing/random.ts +++ b/src/lib/testing/random.ts @@ -5,7 +5,7 @@ import { Json, AccountUpdate, ZkappCommand, - emptyValue, + empty, } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; import { AuthRequired, @@ -26,19 +26,24 @@ import { import { genericLayoutFold } from '../../bindings/lib/from-layout.js'; import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; import { - GenericProvable, + PrimitiveTypeMap, primitiveTypeMap, } from '../../bindings/lib/generic.js'; -import { Scalar, PrivateKey, Group } from '../../provable/curve-bigint.js'; +import { + Scalar, + PrivateKey, + Group, +} from '../../mina-signer/src/curve-bigint.js'; import { Signature } from '../../mina-signer/src/signature.js'; import { randomBytes } from '../../bindings/crypto/random.js'; -import { alphabet } from '../base58.js'; +import { alphabet } from '../util/base58.js'; import { bytesToBigInt } from '../../bindings/crypto/bigint-helpers.js'; import { Memo } from '../../mina-signer/src/memo.js'; -import { ProvableExtended } from '../../bindings/lib/provable-bigint.js'; +import { Signable } from '../../mina-signer/src/derivers-bigint.js'; import { tokenSymbolLength } from '../../bindings/mina-transaction/derived-leaves.js'; import { stringLengthInBytes } from '../../bindings/lib/binable.js'; import { mocks } from '../../bindings/crypto/constants.js'; +import type { FiniteField } from '../../bindings/crypto/finite-field.js'; export { Random, sample, withHardCoded }; @@ -65,8 +70,10 @@ function sample(rng: Random, size: number) { const boolean = Random_(() => drawOneOf8() < 4); const bool = map(boolean, Bool); +const uint8 = biguintWithInvalid(8); const uint32 = biguintWithInvalid(32); const uint64 = biguintWithInvalid(64); +const byte = Random_(drawRandomByte); const field = fieldWithInvalid(Field); const scalar = fieldWithInvalid(Scalar); @@ -79,7 +86,7 @@ const keypair = map(privateKey, (privatekey) => ({ publicKey: PrivateKey.toPublicKey(privatekey), })); -const tokenId = oneOf(TokenId.emptyValue(), field); +const tokenId = oneOf(TokenId.empty(), field); const stateHash = field; const authRequired = map( oneOf( @@ -104,16 +111,16 @@ const actions = mapWithInvalid( array(array(field, int(1, 5)), nat(2)), Actions.fromList ); -const actionState = oneOf(ActionState.emptyValue(), field); -const verificationKeyHash = oneOf(VerificationKeyHash.emptyValue(), field); -const receiptChainHash = oneOf(ReceiptChainHash.emptyValue(), field); +const actionState = oneOf(ActionState.empty(), field); +const verificationKeyHash = oneOf(VerificationKeyHash.empty(), field); +const receiptChainHash = oneOf(ReceiptChainHash.empty(), field); const zkappUri = map(string(nat(50)), ZkappUri.fromJSON); -const PrimitiveMap = primitiveTypeMap(); -type Types = typeof TypeMap & typeof customTypes & typeof PrimitiveMap; -type Provable = GenericProvable; +type Types = typeof TypeMap & typeof customTypes & PrimitiveTypeMap; type Generators = { - [K in keyof Types]: Types[K] extends Provable ? Random : never; + [K in keyof Types]: Types[K] extends Signable + ? Random + : never; }; const Generators: Generators = { Field: field, @@ -137,8 +144,8 @@ const Generators: Generators = { number: nat(3), TransactionVersion: uint32, }; -let typeToBigintGenerator = new Map, Random>( - [TypeMap, PrimitiveMap, customTypes] +let typeToBigintGenerator = new Map, Random>( + [TypeMap, primitiveTypeMap, customTypes] .map(Object.entries) .flat() .map(([key, value]) => [value, Generators[key as keyof Generators]]) @@ -160,7 +167,8 @@ const accountUpdate = mapWithInvalid( a.body.authorizationKind.isProved = Bool(false); } if (!a.body.authorizationKind.isProved) { - a.body.authorizationKind.verificationKeyHash = Field(0); + a.body.authorizationKind.verificationKeyHash = + VerificationKeyHash.empty(); } // ensure mayUseToken is valid let { inheritFromParent, parentsOwnToken } = a.body.mayUseToken; @@ -186,17 +194,20 @@ const nonNumericString = reject( string(nat(20)), (str: any) => !isNaN(str) && !isNaN(parseFloat(str)) ); -const invalidUint64Json = toString( - oneOf(uint64.invalid, nonInteger, nonNumericString) +const invalidUint8Json = toString( + oneOf(uint8.invalid, nonInteger, nonNumericString) ); const invalidUint32Json = toString( oneOf(uint32.invalid, nonInteger, nonNumericString) ); +const invalidUint64Json = toString( + oneOf(uint64.invalid, nonInteger, nonNumericString) +); // some json versions of those types let json_ = { - uint64: { ...toString(uint64), invalid: invalidUint64Json }, uint32: { ...toString(uint32), invalid: invalidUint32Json }, + uint64: { ...toString(uint64), invalid: invalidUint64Json }, publicKey: withInvalidBase58(mapWithInvalid(publicKey, PublicKey.toBase58)), privateKey: withInvalidBase58(map(privateKey, PrivateKey.toBase58)), keypair: map(keypair, ({ privatekey, publicKey }) => ({ @@ -213,7 +224,7 @@ function withInvalidRandomString(rng: Random) { } type JsonGenerators = { - [K in keyof Types]: Types[K] extends ProvableExtended + [K in keyof Types]: Types[K] extends Signable ? Random : never; }; @@ -241,8 +252,8 @@ const JsonGenerators: JsonGenerators = { number: nat(3), TransactionVersion: json_.uint32, }; -let typeToJsonGenerator = new Map, Random>( - [TypeMap, PrimitiveMap, customTypes] +let typeToJsonGenerator = new Map, Random>( + [TypeMap, primitiveTypeMap, customTypes] .map(Object.entries) .flat() .map(([key, value]) => [value, JsonGenerators[key as keyof JsonGenerators]]) @@ -293,6 +304,7 @@ const Random = Object.assign(Random_, { nat, fraction, boolean, + byte, bytes, string, base58, @@ -307,9 +319,13 @@ const Random = Object.assign(Random_, { reject, dice: Object.assign(dice, { ofSize: diceOfSize() }), field, + otherField: fieldWithInvalid, bool, + uint8, uint32, uint64, + biguint: biguintWithInvalid, + bignat: bignatWithInvalid, privateKey, publicKey, scalar, @@ -325,7 +341,13 @@ function generatorFromLayout( { isJson }: { isJson: boolean } ): Random { let typeToGenerator = isJson ? typeToJsonGenerator : typeToBigintGenerator; - return genericLayoutFold, TypeMap, Json.TypeMap>( + return genericLayoutFold< + Signable, + undefined, + Random, + TypeMap, + Json.TypeMap + >( TypeMap, customTypes, { @@ -341,7 +363,7 @@ function generatorFromLayout( return array(element, size); }, reduceObject(keys, object) { - // hack to not sample invalid vk hashes (because vk hash is correlated with other fields, and has to be overriden) + // hack to not sample invalid vk hashes (because vk hash is correlated with other fields, and has to be overridden) if (keys.includes('verificationKeyHash')) { (object as any).verificationKeyHash = noInvalid( (object as any).verificationKeyHash @@ -355,7 +377,7 @@ function generatorFromLayout( } else { return mapWithInvalid(isSome, value, (isSome, value) => { let isSomeBoolean = TypeMap.Bool.toJSON(isSome); - if (!isSomeBoolean) return emptyValue(typeData); + if (!isSomeBoolean) return empty(typeData); return { isSome, value }; }); } @@ -657,14 +679,14 @@ function int(min: number, max: number): Random { * log-uniform distribution over range [0, max] * with bias towards 0, 1, 2 */ -function nat(max: number): Random { - if (max < 0) throw Error('max < 0'); - if (max === 0) return constant(0); +function bignat(max: bigint): Random { + if (max < 0n) throw Error('max < 0'); + if (max === 0n) return constant(0n); let bits = max.toString(2).length; let bitBits = bits.toString(2).length; // set of special numbers that will appear more often in tests - let special = [0, 0, 1]; - if (max > 1) special.push(2); + let special = [0n, 0n, 1n]; + if (max > 1n) special.push(2n); let nSpecial = special.length; return { create: () => () => { @@ -680,13 +702,21 @@ function nat(max: number): Random { let bitLength = 1 + drawUniformUintBits(bitBits); if (bitLength > bits) continue; // draw number from [0, 2**bitLength); reject if > max - let n = drawUniformUintBits(bitLength); + let n = drawUniformBigUintBits(bitLength); if (n <= max) return n; } }, }; } +/** + * log-uniform distribution over range [0, max] + * with bias towards 0, 1, 2 + */ +function nat(max: number): Random { + return map(bignat(BigInt(max)), (n) => Number(n)); +} + function fraction(fixedPrecision = 3) { let denom = 10 ** fixedPrecision; if (fixedPrecision < 1) throw Error('precision must be > 1'); @@ -717,16 +747,15 @@ let specialBytes = [0, 0, 0, 1, 1, 2, 255, 255]; * log-uniform distribution over range [0, 255] * with bias towards 0, 1, 2, 255 */ -const byte: Random = { - create: () => () => { - // 25% of test cases are special numbers - if (drawOneOf8() < 2) return specialBytes[drawOneOf8()]; - // the remaining follow log-uniform / cut off exponential distribution: - // we sample a bit length from [1, 8] and then a number with that length - let bitLength = 1 + drawOneOf8(); - return drawUniformUintBits(bitLength); - }, -}; +function drawRandomByte() { + // 25% of test cases are special numbers + if (drawOneOf8() < 2) return specialBytes[drawOneOf8()]; + // the remaining follow log-uniform / cut off exponential distribution: + // we sample a bit length from [1, 8] and then a number with that length + let bitLength = 1 + drawOneOf8(); + return drawUniformUintBits(bitLength); +} + /** * log-uniform distribution over 2^n-bit range * with bias towards 0, 1, 2, max @@ -825,12 +854,21 @@ function biguintWithInvalid(bits: number): RandomWithInvalid { return Object.assign(valid, { invalid }); } -function fieldWithInvalid( - F: typeof Field | typeof Scalar -): RandomWithInvalid { +function bignatWithInvalid(max: bigint): RandomWithInvalid { + let valid = bignat(max); + let double = bignat(2n * max); + let negative = map(double, (uint) => -uint - 1n); + let tooLarge = map(valid, (uint) => uint + max); + let invalid = oneOf(negative, tooLarge); + return Object.assign(valid, { invalid }); +} + +function fieldWithInvalid(F: FiniteField): RandomWithInvalid { let randomField = Random_(F.random); - let specialField = oneOf(0n, 1n, F(-1)); - let field = oneOf(randomField, randomField, uint64, specialField); + let specialField = oneOf(0n, 1n, F.negate(1n)); + let roughLogSize = 1 << Math.ceil(Math.log2(F.sizeInBits) - 1); + let uint = biguint(roughLogSize); + let field = oneOf(randomField, randomField, uint, specialField); let tooLarge = map(field, (x) => x + F.modulus); let negative = map(field, (x) => -x - 1n); let invalid = oneOf(tooLarge, negative); diff --git a/src/lib/testing/testing.unit-test.ts b/src/lib/testing/testing.unit-test.ts index 4a0abe91fb..6041b155b4 100644 --- a/src/lib/testing/testing.unit-test.ts +++ b/src/lib/testing/testing.unit-test.ts @@ -6,11 +6,11 @@ import { PublicKey, UInt32, UInt64, - provableFromLayout, + signableFromLayout, ZkappCommand, Json, } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; -import { test, Random, sample } from './property.js'; +import { test, Random } from './property.js'; // some trivial roundtrip tests test(Random.accountUpdate, (accountUpdate, assert) => { @@ -20,10 +20,11 @@ test(Random.accountUpdate, (accountUpdate, assert) => { jsonString === JSON.stringify(AccountUpdate.toJSON(AccountUpdate.fromJSON(json))) ); - let fields = AccountUpdate.toFields(accountUpdate); - let auxiliary = AccountUpdate.toAuxiliary(accountUpdate); - let recovered = AccountUpdate.fromFields(fields, auxiliary); - assert(jsonString === JSON.stringify(AccountUpdate.toJSON(recovered))); + // TODO add back using `fromValue` + // let fields = AccountUpdate.toFields(accountUpdate); + // let auxiliary = AccountUpdate.toAuxiliary(accountUpdate); + // let recovered = AccountUpdate.fromFields(fields, auxiliary); + // assert(jsonString === JSON.stringify(AccountUpdate.toJSON(recovered))); }); test(Random.json.accountUpdate, (json) => { let jsonString = JSON.stringify(json); @@ -52,7 +53,7 @@ test.custom({ negative: true, timeBudget: 1000 })( AccountUpdate.fromJSON ); -const FeePayer = provableFromLayout< +const FeePayer = signableFromLayout< ZkappCommand['feePayer'], Json.ZkappCommand['feePayer'] >(jsLayout.ZkappCommand.entries.feePayer as any); diff --git a/src/lib/util/arrays.ts b/src/lib/util/arrays.ts new file mode 100644 index 0000000000..891de81015 --- /dev/null +++ b/src/lib/util/arrays.ts @@ -0,0 +1,14 @@ +import { assert } from './errors.js'; + +export { chunk, chunkString }; + +function chunk(array: T[], size: number): T[][] { + assert(array.length % size === 0, 'invalid input length'); + return Array.from({ length: array.length / size }, (_, i) => + array.slice(size * i, size * (i + 1)) + ); +} + +function chunkString(str: string, size: number): string[] { + return chunk([...str], size).map((c) => c.join('')); +} diff --git a/src/lib/util/assert.ts b/src/lib/util/assert.ts new file mode 100644 index 0000000000..f996b4bede --- /dev/null +++ b/src/lib/util/assert.ts @@ -0,0 +1,12 @@ +export { assert, assertPromise }; + +function assert(stmt: boolean, message?: string): asserts stmt { + if (!stmt) { + throw Error(message ?? 'Assertion failed'); + } +} + +function assertPromise(value: Promise, message?: string): Promise { + assert(value instanceof Promise, message ?? 'Expected a promise'); + return value; +} diff --git a/src/lib/base58.ts b/src/lib/util/base58.ts similarity index 95% rename from src/lib/base58.ts rename to src/lib/util/base58.ts index 778540a263..5862cc2862 100644 --- a/src/lib/base58.ts +++ b/src/lib/util/base58.ts @@ -1,7 +1,7 @@ -import { versionBytes } from '../bindings/crypto/constants.js'; -import { Binable, withVersionNumber } from '../bindings/lib/binable.js'; +import { versionBytes } from '../../bindings/crypto/constants.js'; +import { Binable, withVersionNumber } from '../../bindings/lib/binable.js'; import { sha256 } from 'js-sha256'; -import { changeBase } from '../bindings/crypto/bigint-helpers.js'; +import { changeBase } from '../../bindings/crypto/bigint-helpers.js'; export { toBase58Check, diff --git a/src/lib/base58.unit-test.ts b/src/lib/util/base58.unit-test.ts similarity index 91% rename from src/lib/base58.unit-test.ts rename to src/lib/util/base58.unit-test.ts index 2d09307da6..dca4f6c1ac 100644 --- a/src/lib/base58.unit-test.ts +++ b/src/lib/util/base58.unit-test.ts @@ -1,7 +1,7 @@ import { fromBase58Check, toBase58Check } from './base58.js'; -import { Test } from '../snarky.js'; +import { Test } from '../../snarky.js'; import { expect } from 'expect'; -import { test, Random, withHardCoded } from './testing/property.js'; +import { test, Random, withHardCoded } from '../testing/property.js'; let bytes = withHardCoded( Random.bytes(Random.nat(100)), diff --git a/src/lib/errors.ts b/src/lib/util/errors.ts similarity index 92% rename from src/lib/errors.ts rename to src/lib/util/errors.ts index ba7eebc6ef..3fe584eac2 100644 --- a/src/lib/errors.ts +++ b/src/lib/util/errors.ts @@ -99,7 +99,7 @@ function handleResult(result: any) { * A list of keywords used to filter out unwanted lines from the error stack trace. */ const lineRemovalKeywords = [ - 'snarky_js_node.bc.cjs', + 'o1js_node.bc.cjs', '/builtin/', 'CatchAndPrettifyStacktrace', // Decorator name to remove from stacktrace (covers both class and method decorator) ] as const; @@ -148,15 +148,15 @@ function unwrapMlException(error: E) { } /** - * Trims paths in the stack trace line based on whether it includes 'snarkyjs' or 'opam'. + * Trims paths in the stack trace line based on whether it includes 'o1js' or 'opam'. * * @param stacktracePath - The stack trace line containing the path to trim. * @returns The trimmed stack trace line. */ function trimPaths(stacktracePath: string) { - const includesSnarkyJS = stacktracePath.includes('snarkyjs'); - if (includesSnarkyJS) { - return trimSnarkyJSPath(stacktracePath); + const includesO1js = stacktracePath.includes('o1js'); + if (includesO1js) { + return trimO1jsPath(stacktracePath); } const includesOpam = stacktracePath.includes('opam'); @@ -173,25 +173,25 @@ function trimPaths(stacktracePath: string) { } /** - * Trims the 'snarkyjs' portion of the stack trace line's path. + * Trims the 'o1js' portion of the stack trace line's path. * - * @param stacktraceLine - The stack trace line containing the 'snarkyjs' path to trim. - * @returns The stack trace line with the trimmed 'snarkyjs' path. + * @param stacktraceLine - The stack trace line containing the 'o1js' path to trim. + * @returns The stack trace line with the trimmed 'o1js' path. */ -function trimSnarkyJSPath(stacktraceLine: string) { +function trimO1jsPath(stacktraceLine: string) { const fullPath = getDirectoryPath(stacktraceLine); if (!fullPath) { return stacktraceLine; } - const snarkyJSIndex = fullPath.indexOf('snarkyjs'); - if (snarkyJSIndex === -1) { + const o1jsIndex = fullPath.indexOf('o1js'); + if (o1jsIndex === -1) { return stacktraceLine; } // Grab the text before the parentheses as the prefix const prefix = stacktraceLine.slice(0, stacktraceLine.indexOf('(') + 1); - // Grab the text including and after the snarkyjs path - const updatedPath = fullPath.slice(snarkyJSIndex); + // Grab the text including and after the o1js path + const updatedPath = fullPath.slice(o1jsIndex); return `${prefix}${updatedPath})`; } @@ -255,7 +255,7 @@ function trimWorkspacePath(stacktraceLine: string) { * @returns The extracted directory path or undefined if not found. */ function getDirectoryPath(stacktraceLine: string) { - // Regex to match the path inside the parentheses (e.g. (/home/../snarkyjs/../*.ts)) + // Regex to match the path inside the parentheses (e.g. (/home/../o1js/../*.ts)) const fullPathRegex = /\(([^)]+)\)/; const matchedPaths = stacktraceLine.match(fullPathRegex); if (matchedPaths) { @@ -274,6 +274,9 @@ function Bug(message: string) { /** * Make an assertion. When failing, this will communicate to users it's not their fault but indicates an internal bug. */ -function assert(condition: boolean, message = 'Failed assertion.') { +function assert( + condition: boolean, + message = 'Failed assertion.' +): asserts condition { if (!condition) throw Bug(message); } diff --git a/src/lib/util/fs.ts b/src/lib/util/fs.ts new file mode 100644 index 0000000000..4d08832363 --- /dev/null +++ b/src/lib/util/fs.ts @@ -0,0 +1,5 @@ +import cachedir from 'cachedir'; + +export { writeFileSync, readFileSync, mkdirSync } from 'node:fs'; +export { resolve } from 'node:path'; +export { cachedir as cacheDir }; diff --git a/src/lib/util/fs.web.ts b/src/lib/util/fs.web.ts new file mode 100644 index 0000000000..6810aff2fd --- /dev/null +++ b/src/lib/util/fs.web.ts @@ -0,0 +1,15 @@ +export { + dummy as writeFileSync, + dummy as readFileSync, + dummy as resolve, + dummy as mkdirSync, + cacheDir, +}; + +function dummy() { + throw Error('not implemented'); +} + +function cacheDir() { + return ''; +} diff --git a/src/lib/global-context.ts b/src/lib/util/global-context.ts similarity index 94% rename from src/lib/global-context.ts rename to src/lib/util/global-context.ts index dee457b8db..e07561fc53 100644 --- a/src/lib/global-context.ts +++ b/src/lib/util/global-context.ts @@ -101,6 +101,8 @@ function get(t: Context.t): C { return current.context; } +// FIXME there are many common scenarios where this error occurs, which weren't expected when this was written +// it should list them and help to resolve them let contextConflictMessage = "It seems you're running multiple provers concurrently within" + ' the same JavaScript thread, which, at the moment, is not supported and would lead to bugs.'; diff --git a/src/lib/util/tic-toc.ts b/src/lib/util/tic-toc.ts new file mode 100644 index 0000000000..b9dfd61771 --- /dev/null +++ b/src/lib/util/tic-toc.ts @@ -0,0 +1,18 @@ +/** + * Helper for printing timings, in the spirit of Python's `tic` and `toc`. + */ + +export { tic, toc }; + +let timingStack: [string | undefined, number][] = []; + +function tic(label?: string) { + timingStack.push([label, performance.now()]); +} + +function toc() { + let [label, start] = timingStack.pop()!; + let time = (performance.now() - start) / 1000; + if (label) console.log(`${label}... ${time.toFixed(3)} sec`); + return time; +} diff --git a/src/lib/util/types.ts b/src/lib/util/types.ts new file mode 100644 index 0000000000..2bc40500d1 --- /dev/null +++ b/src/lib/util/types.ts @@ -0,0 +1,57 @@ +import { assert } from './errors.js'; + +export { AnyFunction, Tuple, TupleN, AnyTuple, TupleMap }; + +type AnyFunction = (...args: any) => any; + +type Tuple = [T, ...T[]] | []; +type AnyTuple = Tuple; + +type TupleMap, B> = [ + ...{ + [i in keyof T]: B; + } +]; + +const Tuple = { + map, B>( + tuple: T, + f: (a: T[number]) => B + ): TupleMap { + return tuple.map(f) as any; + }, +}; + +/** + * tuple type that has the length as generic parameter + */ +type TupleN = N extends N + ? number extends N + ? [...T[]] // N is not typed as a constant => fall back to array + : [...TupleRec] + : never; + +const TupleN = { + map, B>( + tuple: T, + f: (a: T[number]) => B + ): TupleMap { + return tuple.map(f) as any; + }, + + fromArray(n: N, arr: T[]): TupleN { + assert( + arr.length === n, + `Expected array of length ${n}, got ${arr.length}` + ); + return arr as any; + }, + + hasLength(n: N, tuple: T[]): tuple is TupleN { + return tuple.length === n; + }, +}; + +type TupleRec = R['length'] extends N + ? R + : TupleRec; diff --git a/src/mina b/src/mina index 4d81614a94..deadbd333c 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 4d81614a94ee68ae9afe350e46dbb878852695a7 +Subproject commit deadbd333c04399033c8b573cb0b337e9b33ea41 diff --git a/src/mina-signer/build-web.sh b/src/mina-signer/build-web.sh index d3a0305851..b6aa9558e8 100755 --- a/src/mina-signer/build-web.sh +++ b/src/mina-signer/build-web.sh @@ -3,5 +3,5 @@ set -e tsc -p ../../tsconfig.mina-signer-web.json node moveWebFiles.js -npx esbuild --bundle --minify dist/tmp/mina-signer/MinaSigner.js --outfile=./dist/web/index.js --format=esm --target=es2021 +npx esbuild --bundle --minify dist/tmp/mina-signer/mina-signer.js --outfile=./dist/web/index.js --format=esm --target=es2021 npx rimraf dist/tmp diff --git a/src/mina-signer/index.cjs b/src/mina-signer/index.cjs index ac595416dd..22639a9abe 100644 --- a/src/mina-signer/index.cjs +++ b/src/mina-signer/index.cjs @@ -1,5 +1,6 @@ // this file is a wrapper for supporting commonjs imports -let Client = require('./MinaSigner.js'); +const Client = require('./mina-signer.js'); module.exports = Client.default; +module.exports.Client = Client.default; diff --git a/src/mina-signer/index.d.ts b/src/mina-signer/index.d.ts index d021add39e..c0417d103f 100644 --- a/src/mina-signer/index.d.ts +++ b/src/mina-signer/index.d.ts @@ -1,5 +1,6 @@ // this file is a wrapper for supporting types in both commonjs and esm projects -import Client from './MinaSigner.js'; +import Client, { type NetworkId } from './mina-signer.ts'; -export = Client; +export default Client; +export { Client, type NetworkId }; diff --git a/src/mina-signer/MinaSigner.ts b/src/mina-signer/mina-signer.ts similarity index 79% rename from src/mina-signer/MinaSigner.ts rename to src/mina-signer/mina-signer.ts index cc0d0df274..1b0da67db5 100644 --- a/src/mina-signer/MinaSigner.ts +++ b/src/mina-signer/mina-signer.ts @@ -1,6 +1,6 @@ -import { PrivateKey, PublicKey } from '../provable/curve-bigint.js'; -import * as Json from './src/TSTypes.js'; -import type { SignedLegacy, Signed, Network } from './src/TSTypes.js'; +import { PrivateKey, PublicKey } from './src/curve-bigint.js'; +import * as Json from './src/types.js'; +import type { SignedLegacy, Signed, NetworkId } from './src/types.js'; import { isPayment, @@ -10,7 +10,7 @@ import { isSignedZkappCommand, isStakeDelegation, isZkappCommand, -} from './src/Utils.js'; +} from './src/utils.js'; import * as TransactionJson from '../bindings/mina-transaction/gen/transaction-json.js'; import { ZkappCommand } from '../bindings/mina-transaction/gen/transaction-bigint.js'; import { @@ -34,22 +34,15 @@ import { import { sign, Signature, verify } from './src/signature.js'; import { createNullifier } from './src/nullifier.js'; -export { Client as default }; +export { Client, Client as default, type NetworkId }; const defaultValidUntil = '4294967295'; class Client { - private network: Network; + private network: NetworkId; - constructor(options: { network: Network }) { - if (!options?.network) { - throw Error('Invalid Specified Network'); - } - const specifiedNetwork = options.network.toLowerCase(); - if (specifiedNetwork !== 'mainnet' && specifiedNetwork !== 'testnet') { - throw Error('Invalid Specified Network'); - } - this.network = specifiedNetwork; + constructor({ network }: { network: NetworkId }) { + this.network = network; } /** @@ -85,9 +78,9 @@ class Client { ) { throw Error('Public key not derivable from private key'); } - let dummy = ZkappCommand.toJSON(ZkappCommand.emptyValue()); + let dummy = ZkappCommand.toJSON(ZkappCommand.empty()); dummy.feePayer.body.publicKey = publicKey; - dummy.memo = Memo.toBase58(Memo.emptyValue()); + dummy.memo = Memo.toBase58(Memo.empty()); let signed = signZkappCommand(dummy, privateKey, this.network); let ok = verifyZkappCommandSignature(signed, publicKey, this.network); if (!ok) throw Error('Could not sign a transaction with private key'); @@ -106,14 +99,52 @@ class Client { return PublicKey.toBase58(publicKey); } + /** + * Derives the public key corresponding to a given private key. This function addresses compatibility with private keys generated by external tools that may produce keys outside the domain of the Pallas curve, that was previously accepted by the older [client_sdk](https://www.npmjs.com/package/@o1labs/client-sdk). + * The function first converts the input private key (in Base58 format) to a format that is compatible with the domain of the Pallas curve by applying a modulus operation. This step ensures backward compatibility with older keys that may not directly fit the Pallas curve's domain. Once the private key is in the correct domain, it is used to derive the corresponding public key. + * @param privateKeyBase58 - The private key (in Base58 format) used to derive the corresponding public key. The key is expected to be out of the domain of the Pallas curve and will be converted to fit within the domain as part of this process. + * @returns {Json.PublicKey} The derived public key in Base58 format, corresponding to the input private key, now within the domain of the Pallas curve. + * @remarks + * This function is labeled as "unsafe" due to the modulus operation applied to ensure backward compatibility, which might not adhere to strict security protocols expected in [mina-signer](https://www.npmjs.com/package/mina-signer). It is primarily intended for use cases requiring interoperability with keys managed by previous versions of the [client_sdk](https://www.npmjs.com/package/@o1labs/client-sdk) or other tools that may produce keys outside the Pallas curve's domain. + * It is an essential tool for migrating old keys for use with the current [mina-signer](https://www.npmjs.com/package/mina-signer) library, by allowing keys that would otherwise be rejected to be used effectively. + * + * @example + * ```ts + * // Assuming `oldPrivateKeyBase58` is a private key in Base58 format from an older client SDK + * const publicKeyBase58 = derivePublicKeyUnsafe(oldPrivateKeyBase58); + * console.log(publicKeyBase58); // Logs the derived public key in Base58 format + * ``` + */ + derivePublicKeyUnsafe(privateKeyBase58: Json.PrivateKey): Json.PublicKey { + let privateKey = PrivateKey.fromBase58( + PrivateKey.convertPrivateKeyToBase58WithMod(privateKeyBase58) + ); + let publicKey = PrivateKey.toPublicKey(privateKey); + return PublicKey.toBase58(publicKey); + } + + /** + * Converts a private key that is out of the domain of the Pallas curve to a private key in base58 format that is in the domain by taking the modulus of the private key. + * This is done to keep backwards compatibility with the previous version of the [client_sdk](https://www.npmjs.com/package/@o1labs/client-sdk), which did the same thing when converting a private key to base58. + * @param keyBase58 - The private key that is out of the domain of the Pallas curve + * @returns The private key that is in the domain of the Pallas curve + * @remarks + * This function is particularly useful when migrating old keys to be used by the current [mina-signer](https://www.npmjs.com/package/mina-signer) library, + * which may reject keys that do not fit the domain of the Pallas curve, by performing a modulus operation on the key, it ensures that keys + * from the older client_sdk can be made compatible. + */ + convertPrivateKeyToBase58WithMod(keyBase58: string): string { + return PrivateKey.convertPrivateKeyToBase58WithMod(keyBase58); + } + /** * Signs an arbitrary list of field elements in a SNARK-compatible way. - * The resulting signature can be verified in SnarkyJS as follows: + * The resulting signature can be verified in o1js as follows: * ```ts * // sign field elements with mina-signer * let signed = client.signFields(fields, privateKey); * - * // read signature in snarkyjs and verify + * // read signature in o1js and verify * let signature = Signature.fromBase58(signed.signature); * let isValid: Bool = signature.verify(publicKey, fields.map(Field)); * ``` @@ -493,6 +524,15 @@ class Client { let sk = PrivateKey.fromBase58(privateKeyBase58); return createNullifier(message, sk); } + + /** + * Returns the network ID. + * + * @returns {NetworkId} The network ID. + */ + get networkId(): NetworkId { + return this.network; + } } function validNonNegative(n: number | string | bigint): string { diff --git a/src/mina-signer/package-lock.json b/src/mina-signer/package-lock.json index 74af4ac777..43dedd3ba2 100644 --- a/src/mina-signer/package-lock.json +++ b/src/mina-signer/package-lock.json @@ -1,12 +1,12 @@ { "name": "mina-signer", - "version": "2.0.3", + "version": "3.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mina-signer", - "version": "2.0.3", + "version": "3.0.5", "license": "Apache-2.0", "dependencies": { "blakejs": "^1.2.1", diff --git a/src/mina-signer/package.json b/src/mina-signer/package.json index e215b773bd..ac3f4c4b4a 100644 --- a/src/mina-signer/package.json +++ b/src/mina-signer/package.json @@ -1,7 +1,7 @@ { "name": "mina-signer", "description": "Node API for signing transactions on various networks for Mina Protocol", - "version": "2.1.0", + "version": "3.0.5", "type": "module", "scripts": { "build": "tsc -p ../../tsconfig.mina-signer.json", @@ -18,14 +18,15 @@ "author": "o1labs", "license": "Apache-2.0", "homepage": "https://minaprotocol.com/", - "repository": "https://github.com/o1-labs/snarkyjs", - "bugs": "https://github.com/o1-labs/snarkyjs/issues", - "main": "dist/node/mina-signer/MinaSigner.js", + "repository": "https://github.com/o1-labs/o1js", + "bugs": "https://github.com/o1-labs/o1js/issues", + "main": "dist/node/mina-signer/mina-signer.js", "types": "dist/node/mina-signer/index.d.ts", "exports": { + "types": "./dist/node/mina-signer/mina-signer.d.ts", "web": "./dist/web/index.js", "require": "./dist/node/mina-signer/index.cjs", - "node": "./dist/node/mina-signer/MinaSigner.js", + "node": "./dist/node/mina-signer/mina-signer.js", "default": "./dist/web/index.js" }, "files": [ diff --git a/src/provable/curve-bigint.ts b/src/mina-signer/src/curve-bigint.ts similarity index 78% rename from src/provable/curve-bigint.ts rename to src/mina-signer/src/curve-bigint.ts index 2cd34796f2..79e6708594 100644 --- a/src/provable/curve-bigint.ts +++ b/src/mina-signer/src/curve-bigint.ts @@ -1,18 +1,17 @@ -import { Fq, mod } from '../bindings/crypto/finite_field.js'; -import { GroupProjective, Pallas } from '../bindings/crypto/elliptic_curve.js'; -import { versionBytes } from '../bindings/crypto/constants.js'; +import { Fq, mod } from '../../bindings/crypto/finite-field.js'; +import { + GroupProjective, + Pallas, +} from '../../bindings/crypto/elliptic-curve.js'; +import { versionBytes } from '../../bindings/crypto/constants.js'; import { record, withCheck, withVersionNumber, -} from '../bindings/lib/binable.js'; -import { base58, withBase58 } from '../lib/base58.js'; +} from '../../bindings/lib/binable.js'; +import { base58, withBase58 } from '../../lib/util/base58.js'; import { Bool, checkRange, Field, pseudoClass } from './field-bigint.js'; -import { - BinableBigint, - ProvableBigint, - provable, -} from '../bindings/lib/provable-bigint.js'; +import { BinableBigint, SignableBigint, signable } from './derivers-bigint.js'; import { HashInputLegacy } from './poseidon-bigint.js'; export { Group, PublicKey, Scalar, PrivateKey, versionNumbers }; @@ -79,7 +78,7 @@ let BinablePublicKey = withVersionNumber( * A public key, represented by a non-zero point on the Pallas curve, in compressed form { x, isOdd } */ const PublicKey = { - ...provable({ x: Field, isOdd: Bool }, { customObjectKeys: ['x', 'isOdd'] }), + ...signable({ x: Field, isOdd: Bool }), ...withBase58(BinablePublicKey, versionBytes.publicKey), toJSON(publicKey: PublicKey) { @@ -123,7 +122,7 @@ const Scalar = pseudoClass( return mod(BigInt(value), Fq.modulus); }, { - ...ProvableBigint(checkScalar), + ...SignableBigint(checkScalar), ...BinableBigint(Fq.sizeInBits, checkScalar), ...Fq, } @@ -137,10 +136,25 @@ let Base58PrivateKey = base58(BinablePrivateKey, versionBytes.privateKey); */ const PrivateKey = { ...Scalar, - ...provable(Scalar), + ...signable(Scalar), ...Base58PrivateKey, ...BinablePrivateKey, toPublicKey(key: PrivateKey) { return PublicKey.fromGroup(Group.scale(Group.generatorMina, key)); }, + convertPrivateKeyToBase58WithMod, }; + +const Bigint256 = BinableBigint(256, () => { + // no check supplied, allows any string of 256 bits +}); +const OutOfDomainKey = base58( + withVersionNumber(Bigint256, versionNumbers.scalar), + versionBytes.privateKey +); + +function convertPrivateKeyToBase58WithMod(keyBase58: string): string { + let key = OutOfDomainKey.fromBase58(keyBase58); + key = mod(key, Fq.modulus); + return PrivateKey.toBase58(key); +} diff --git a/src/mina-signer/src/derivers-bigint.ts b/src/mina-signer/src/derivers-bigint.ts new file mode 100644 index 0000000000..936c6f1246 --- /dev/null +++ b/src/mina-signer/src/derivers-bigint.ts @@ -0,0 +1,80 @@ +import { bigIntToBytes } from '../../bindings/crypto/bigint-helpers.js'; +import { createDerivers } from '../../bindings/lib/provable-generic.js'; +import { + GenericHashInput, + GenericProvableExtended, + GenericSignable, +} from '../../bindings/lib/generic.js'; +import { + BinableWithBits, + defineBinable, + withBits, +} from '../../bindings/lib/binable.js'; + +export { + signable, + ProvableExtended, + SignableBigint, + BinableBigint, + HashInput, + Signable, +}; + +type Field = bigint; + +let { signable } = createDerivers(); + +type Signable = GenericSignable; +type ProvableExtended = GenericProvableExtended; +type HashInput = GenericHashInput; + +function SignableBigint< + T extends bigint = bigint, + TJSON extends string = string +>(check: (x: bigint) => void): Signable { + return { + toInput(x) { + return { fields: [x], packed: [] }; + }, + toJSON(x) { + return x.toString() as TJSON; + }, + fromJSON(json) { + if (isNaN(json as any) || isNaN(parseFloat(json))) { + throw Error(`fromJSON: expected a numeric string, got "${json}"`); + } + let x = BigInt(json) as T; + check(x); + return x; + }, + empty() { + return 0n as T; + }, + }; +} + +function BinableBigint( + sizeInBits: number, + check: (x: bigint) => void +): BinableWithBits { + let sizeInBytes = Math.ceil(sizeInBits / 8); + return withBits( + defineBinable({ + toBytes(x) { + return bigIntToBytes(x, sizeInBytes); + }, + readBytes(bytes, start) { + let x = 0n; + let bitPosition = 0n; + let end = Math.min(start + sizeInBytes, bytes.length); + for (let i = start; i < end; i++) { + x += BigInt(bytes[i]) << bitPosition; + bitPosition += 8n; + } + check(x); + return [x as T, end]; + }, + }), + sizeInBits + ); +} diff --git a/src/provable/field-bigint.ts b/src/mina-signer/src/field-bigint.ts similarity index 86% rename from src/provable/field-bigint.ts rename to src/mina-signer/src/field-bigint.ts index ea2d797c83..137b370668 100644 --- a/src/provable/field-bigint.ts +++ b/src/mina-signer/src/field-bigint.ts @@ -1,12 +1,9 @@ -import { randomBytes } from '../bindings/crypto/random.js'; -import { Fp, mod } from '../bindings/crypto/finite_field.js'; -import { - BinableBigint, - HashInput, - ProvableBigint, -} from '../bindings/lib/provable-bigint.js'; +import { randomBytes } from '../../bindings/crypto/random.js'; +import { Fp, mod } from '../../bindings/crypto/finite-field.js'; +import { BinableBigint, HashInput, SignableBigint } from './derivers-bigint.js'; export { Field, Bool, UInt32, UInt64, Sign }; +export { BinableFp, SignableFp }; export { pseudoClass, sizeInBits, checkRange, checkField }; type Field = bigint; @@ -26,6 +23,9 @@ const checkField = checkRange(0n, Fp.modulus, 'Field'); const checkBool = checkAllowList(new Set([0n, 1n]), 'Bool'); const checkSign = checkAllowList(new Set([1n, minusOne]), 'Sign'); +const BinableFp = BinableBigint(Fp.sizeInBits, checkField); +const SignableFp = SignableBigint(checkField); + /** * The base field of the Pallas curve */ @@ -33,11 +33,7 @@ const Field = pseudoClass( function Field(value: bigint | number | string): Field { return mod(BigInt(value), Fp.modulus); }, - { - ...ProvableBigint(checkField), - ...BinableBigint(Fp.sizeInBits, checkField), - ...Fp, - } + { ...SignableFp, ...BinableFp, ...Fp } ); /** @@ -48,7 +44,7 @@ const Bool = pseudoClass( return BigInt(value) as Bool; }, { - ...ProvableBigint(checkBool), + ...SignableBigint(checkBool), ...BinableBigint(1, checkBool), toInput(x: Bool): HashInput { return { fields: [], packed: [[x, 1]] }; @@ -64,9 +60,7 @@ const Bool = pseudoClass( checkBool(x); return x; }, - sizeInBytes() { - return 1; - }, + sizeInBytes: 1, fromField(x: Field) { checkBool(x); return x as 0n | 1n; @@ -87,7 +81,7 @@ function Unsigned(bits: number) { return x; }, { - ...ProvableBigint(checkUnsigned), + ...SignableBigint(checkUnsigned), ...binable, toInput(x: bigint): HashInput { return { fields: [], packed: [[x, bits]] }; @@ -109,9 +103,9 @@ const Sign = pseudoClass( return mod(BigInt(value), Fp.modulus) as Sign; }, { - ...ProvableBigint(checkSign), + ...SignableBigint(checkSign), ...BinableBigint(1, checkSign), - emptyValue() { + empty() { return 1n; }, toInput(x: Sign): HashInput { diff --git a/src/mina-signer/src/memo.ts b/src/mina-signer/src/memo.ts index 976c822ca6..ad58c4353e 100644 --- a/src/mina-signer/src/memo.ts +++ b/src/mina-signer/src/memo.ts @@ -6,13 +6,13 @@ import { stringToBytes, withBits, } from '../../bindings/lib/binable.js'; -import { base58 } from '../../lib/base58.js'; +import { base58 } from '../../lib/util/base58.js'; import { HashInputLegacy, hashWithPrefix, packToFieldsLegacy, prefixes, -} from '../../provable/poseidon-bigint.js'; +} from './poseidon-bigint.js'; import { versionBytes } from '../../bindings/crypto/constants.js'; export { Memo }; @@ -62,10 +62,8 @@ const Memo = { hash, ...withBits(Binable, SIZE * 8), ...base58(Binable, versionBytes.userCommandMemo), - sizeInBytes() { - return SIZE; - }, - emptyValue() { + sizeInBytes: SIZE, + empty() { return Memo.fromString(''); }, toValidString(memo = '') { diff --git a/src/mina-signer/src/nullifier.ts b/src/mina-signer/src/nullifier.ts index 5d6f7c5c6d..103f30fa8b 100644 --- a/src/mina-signer/src/nullifier.ts +++ b/src/mina-signer/src/nullifier.ts @@ -1,13 +1,8 @@ -import { Fq } from '../../bindings/crypto/finite_field.js'; +import { Fq } from '../../bindings/crypto/finite-field.js'; import { Poseidon } from '../../bindings/crypto/poseidon.js'; -import { - Group, - PublicKey, - Scalar, - PrivateKey, -} from '../../provable/curve-bigint.js'; -import { Field } from '../../provable/field-bigint.js'; -import { Nullifier } from './TSTypes.js'; +import { Group, PublicKey, Scalar, PrivateKey } from './curve-bigint.js'; +import { Field } from './field-bigint.js'; +import { Nullifier } from './types.js'; export { createNullifier }; diff --git a/src/provable/poseidon-bigint.ts b/src/mina-signer/src/poseidon-bigint.ts similarity index 81% rename from src/provable/poseidon-bigint.ts rename to src/mina-signer/src/poseidon-bigint.ts index 2ef684d585..7d8676c2b4 100644 --- a/src/provable/poseidon-bigint.ts +++ b/src/mina-signer/src/poseidon-bigint.ts @@ -1,13 +1,13 @@ import { Field, sizeInBits } from './field-bigint.js'; -import { Poseidon, PoseidonLegacy } from '../bindings/crypto/poseidon.js'; -import { prefixes } from '../bindings/crypto/constants.js'; -import { createHashInput } from '../bindings/lib/provable-generic.js'; -import { GenericHashInput } from '../bindings/lib/generic.js'; -import { createHashHelpers } from '../lib/hash-generic.js'; +import { Poseidon, PoseidonLegacy } from '../../bindings/crypto/poseidon.js'; +import { prefixes } from '../../bindings/crypto/constants.js'; +import { createHashInput } from '../../bindings/lib/provable-generic.js'; +import { GenericHashInput } from '../../bindings/lib/generic.js'; +import { createHashHelpers } from '../../lib/provable/crypto/hash-generic.js'; export { Poseidon, - Hash, + HashHelpers, HashInput, prefixes, packToFields, @@ -20,8 +20,8 @@ export { type HashInput = GenericHashInput; const HashInput = createHashInput(); -const Hash = createHashHelpers(Field, Poseidon); -let { hashWithPrefix } = Hash; +const HashHelpers = createHashHelpers(Field, Poseidon); +let { hashWithPrefix } = HashHelpers; const HashLegacy = createHashHelpers(Field, PoseidonLegacy); diff --git a/src/mina-signer/src/random-transaction.ts b/src/mina-signer/src/random-transaction.ts index 80ecf9d7f6..ac475348e5 100644 --- a/src/mina-signer/src/random-transaction.ts +++ b/src/mina-signer/src/random-transaction.ts @@ -5,8 +5,9 @@ import { PublicKey, ZkappCommand, } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; -import { PrivateKey } from '../../provable/curve-bigint.js'; -import { NetworkId, Signature } from './signature.js'; +import { PrivateKey } from './curve-bigint.js'; +import { Signature } from './signature.js'; +import { NetworkId } from './types.js'; export { RandomTransaction }; @@ -140,5 +141,8 @@ const RandomTransaction = { zkappCommand, zkappCommandAndFeePayerKey, zkappCommandJson, - networkId: Random.oneOf('testnet', 'mainnet'), + networkId: Random.oneOf('testnet', 'mainnet', { + custom: 'other', + }), + accountUpdateWithCallDepth: accountUpdate, }; diff --git a/src/mina-signer/src/rosetta.ts b/src/mina-signer/src/rosetta.ts index 395a078ce1..c95533c47b 100644 --- a/src/mina-signer/src/rosetta.ts +++ b/src/mina-signer/src/rosetta.ts @@ -1,6 +1,6 @@ import { Binable } from '../../bindings/lib/binable.js'; -import { PublicKey, Scalar } from '../../provable/curve-bigint.js'; -import { Field } from '../../provable/field-bigint.js'; +import { PublicKey, Scalar } from './curve-bigint.js'; +import { Field } from './field-bigint.js'; import { Memo } from './memo.js'; import { Signature } from './signature.js'; @@ -27,12 +27,15 @@ function fieldToHex( ) { let bytes = binable.toBytes(x); // set highest bit (which is empty) - bytes[bytes.length - 1] &= Number(paddingBit) << 7; - // map each byte to a hex string of length 2 + bytes[bytes.length - 1] |= Number(paddingBit) << 7; + // map each byte to a 0-padded hex string of length 2 return bytes - .map((byte) => byte.toString(16).split('').reverse().join('')) + .map((byte) => + byte.toString(16).padStart(2, '0').split('').reverse().join('') + ) .join(''); } + function fieldFromHex( binable: Binable, hex: string diff --git a/src/mina-signer/src/sign-legacy.ts b/src/mina-signer/src/sign-legacy.ts index 2cc71b9e98..0deef8dd07 100644 --- a/src/mina-signer/src/sign-legacy.ts +++ b/src/mina-signer/src/sign-legacy.ts @@ -1,16 +1,16 @@ -import { UInt32, UInt64 } from '../../provable/field-bigint.js'; -import { PrivateKey, PublicKey } from '../../provable/curve-bigint.js'; -import { HashInputLegacy } from '../../provable/poseidon-bigint.js'; +import { UInt32, UInt64 } from './field-bigint.js'; +import { PrivateKey, PublicKey } from './curve-bigint.js'; +import { HashInputLegacy } from './poseidon-bigint.js'; import { Memo } from './memo.js'; import { SignatureJson, - NetworkId, Signature, signLegacy, verifyLegacy, } from './signature.js'; import { Json } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; import { bytesToBits, stringToBytes } from '../../bindings/lib/binable.js'; +import { NetworkId } from './types.js'; export { signPayment, diff --git a/src/mina-signer/src/sign-legacy.unit-test.ts b/src/mina-signer/src/sign-legacy.unit-test.ts index f414f1c7fa..3654943682 100644 --- a/src/mina-signer/src/sign-legacy.unit-test.ts +++ b/src/mina-signer/src/sign-legacy.unit-test.ts @@ -14,12 +14,13 @@ import { verifyStakeDelegation, verifyStringSignature, } from './sign-legacy.js'; -import { NetworkId, Signature, SignatureJson } from './signature.js'; +import { Signature, SignatureJson } from './signature.js'; import { expect } from 'expect'; -import { PublicKey, Scalar } from '../../provable/curve-bigint.js'; -import { Field } from '../../provable/field-bigint.js'; +import { PublicKey, Scalar } from './curve-bigint.js'; +import { Field } from './field-bigint.js'; import { Random, test } from '../../lib/testing/property.js'; import { RandomTransaction } from './random-transaction.js'; +import { NetworkId } from './types.js'; let { privateKey, publicKey } = keypair; let networks: NetworkId[] = ['testnet', 'mainnet']; @@ -28,7 +29,7 @@ let networks: NetworkId[] = ['testnet', 'mainnet']; for (let network of networks) { let i = 0; - let reference = signatures[network]; + let reference = signatures[NetworkId.toString(network)]; for (let payment of payments) { let signature = signPayment(payment, privateKey, network); diff --git a/src/mina-signer/src/sign-zkapp-command.ts b/src/mina-signer/src/sign-zkapp-command.ts index 69578ed193..51b296be95 100644 --- a/src/mina-signer/src/sign-zkapp-command.ts +++ b/src/mina-signer/src/sign-zkapp-command.ts @@ -1,23 +1,20 @@ -import { Bool, Field, Sign, UInt32 } from '../../provable/field-bigint.js'; -import { PrivateKey, PublicKey } from '../../provable/curve-bigint.js'; +import { Bool, Field, Sign, UInt32 } from './field-bigint.js'; +import { PrivateKey, PublicKey } from './curve-bigint.js'; import { Json, AccountUpdate, ZkappCommand, } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; -import { - hashWithPrefix, - packToFields, - prefixes, -} from '../../provable/poseidon-bigint.js'; +import { hashWithPrefix, packToFields, prefixes } from './poseidon-bigint.js'; import { Memo } from './memo.js'; import { - NetworkId, Signature, signFieldElement, verifyFieldElement, + zkAppBodyPrefix, } from './signature.js'; import { mocks } from '../../bindings/crypto/constants.js'; +import { NetworkId } from './types.js'; // external API export { signZkappCommand, verifyZkappCommandSignature }; @@ -28,11 +25,13 @@ export { verifyAccountUpdateSignature, accountUpdatesToCallForest, callForestHash, + callForestHashGeneric, accountUpdateHash, feePayerHash, createFeePayer, accountUpdateFromFeePayer, isCallDepthValid, + CallForest, }; function signZkappCommand( @@ -42,7 +41,10 @@ function signZkappCommand( ): Json.ZkappCommand { let zkappCommand = ZkappCommand.fromJSON(zkappCommand_); - let { commitment, fullCommitment } = transactionCommitments(zkappCommand); + let { commitment, fullCommitment } = transactionCommitments( + zkappCommand, + networkId + ); let privateKey = PrivateKey.fromBase58(privateKeyBase58); let publicKey = zkappCommand.feePayer.body.publicKey; @@ -69,7 +71,10 @@ function verifyZkappCommandSignature( ) { let zkappCommand = ZkappCommand.fromJSON(zkappCommand_); - let { commitment, fullCommitment } = transactionCommitments(zkappCommand); + let { commitment, fullCommitment } = transactionCommitments( + zkappCommand, + networkId + ); let publicKey = PublicKey.fromBase58(publicKeyBase58); // verify fee payer signature @@ -106,14 +111,17 @@ function verifyAccountUpdateSignature( return verifyFieldElement(signature, usedCommitment, publicKey, networkId); } -function transactionCommitments(zkappCommand: ZkappCommand) { +function transactionCommitments( + zkappCommand: ZkappCommand, + networkId: NetworkId +) { if (!isCallDepthValid(zkappCommand)) { throw Error('zkapp command: invalid call depth'); } let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); - let commitment = callForestHash(callForest); + let commitment = callForestHash(callForest, networkId); let memoHash = Memo.hash(Memo.fromBase58(zkappCommand.memo)); - let feePayerDigest = feePayerHash(zkappCommand.feePayer); + let feePayerDigest = feePayerHash(zkappCommand.feePayer, networkId); let fullCommitment = hashWithPrefix(prefixes.accountUpdateCons, [ memoHash, feePayerDigest, @@ -122,16 +130,22 @@ function transactionCommitments(zkappCommand: ZkappCommand) { return { commitment, fullCommitment }; } -type CallTree = { accountUpdate: AccountUpdate; children: CallForest }; -type CallForest = CallTree[]; +type CallTree = { + accountUpdate: AccountUpdate; + children: CallForest; +}; +type CallForest = CallTree[]; /** * Turn flat list into a hierarchical structure (forest) by letting the callDepth * determine parent-child relationships */ -function accountUpdatesToCallForest(updates: AccountUpdate[], callDepth = 0) { +function accountUpdatesToCallForest( + updates: A[], + callDepth = 0 +) { let remainingUpdates = callDepth > 0 ? updates : [...updates]; - let forest: CallForest = []; + let forest: CallForest = []; while (remainingUpdates.length > 0) { let accountUpdate = remainingUpdates[0]; if (accountUpdate.body.callDepth < callDepth) return forest; @@ -142,18 +156,43 @@ function accountUpdatesToCallForest(updates: AccountUpdate[], callDepth = 0) { return forest; } -function accountUpdateHash(update: AccountUpdate) { +function accountUpdateHash(update: AccountUpdate, networkId: NetworkId) { assertAuthorizationKindValid(update); let input = AccountUpdate.toInput(update); let fields = packToFields(input); - return hashWithPrefix(prefixes.body, fields); + return hashWithPrefix(zkAppBodyPrefix(networkId), fields); } -function callForestHash(forest: CallForest): Field { - let stackHash = 0n; +function callForestHash( + forest: CallForest, + networkId: NetworkId +): bigint { + return callForestHashGeneric( + forest, + accountUpdateHash, + hashWithPrefix, + 0n, + networkId + ); +} + +function callForestHashGeneric( + forest: CallForest, + hash: (a: A, networkId: NetworkId) => F, + hashWithPrefix: (prefix: string, input: F[]) => F, + emptyHash: F, + networkId: NetworkId +): F { + let stackHash = emptyHash; for (let callTree of [...forest].reverse()) { - let calls = callForestHash(callTree.children); - let treeHash = accountUpdateHash(callTree.accountUpdate); + let calls = callForestHashGeneric( + callTree.children, + hash, + hashWithPrefix, + emptyHash, + networkId + ); + let treeHash = hash(callTree.accountUpdate, networkId); let nodeHash = hashWithPrefix(prefixes.accountUpdateNode, [ treeHash, calls, @@ -171,16 +210,16 @@ type FeePayer = ZkappCommand['feePayer']; function createFeePayer(feePayer: FeePayer['body']): FeePayer { return { authorization: '', body: feePayer }; } -function feePayerHash(feePayer: FeePayer) { +function feePayerHash(feePayer: FeePayer, networkId: NetworkId) { let accountUpdate = accountUpdateFromFeePayer(feePayer); - return accountUpdateHash(accountUpdate); + return accountUpdateHash(accountUpdate, networkId); } function accountUpdateFromFeePayer({ body: { fee, nonce, publicKey, validUntil }, authorization: signature, }: FeePayer): AccountUpdate { - let { body } = AccountUpdate.emptyValue(); + let { body } = AccountUpdate.empty(); body.publicKey = publicKey; body.balanceChange = { magnitude: fee, sgn: Sign(-1) }; body.incrementNonce = Bool(true); diff --git a/src/mina-signer/src/sign-zkapp-command.unit-test.ts b/src/mina-signer/src/sign-zkapp-command.unit-test.ts index 44e5c06b0c..3b39f330b4 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -1,14 +1,5 @@ import { expect } from 'expect'; -import { Ledger, Test, Pickles } from '../../snarky.js'; -import { - PrivateKey as PrivateKeySnarky, - PublicKey as PublicKeySnarky, -} from '../../lib/signature.js'; -import { - AccountUpdate as AccountUpdateSnarky, - ZkappCommand as ZkappCommandSnarky, -} from '../../lib/account_update.js'; -import { PrivateKey, PublicKey } from '../../provable/curve-bigint.js'; +import { mocks } from '../../bindings/crypto/constants.js'; import { AccountUpdate, Field, @@ -16,6 +7,24 @@ import { ZkappCommand, } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; import * as TypesSnarky from '../../bindings/mina-transaction/gen/transaction.js'; +import { + AccountUpdate as AccountUpdateSnarky, + ZkappCommand as ZkappCommandSnarky, +} from '../../lib/mina/account-update.js'; +import { FieldConst } from '../../lib/provable/core/fieldvar.js'; +import { packToFields as packToFieldsSnarky } from '../../lib/provable/crypto/poseidon.js'; +import { Network, setActiveInstance } from '../../lib/mina/mina.js'; +import { Ml, MlHashInput } from '../../lib/ml/conversion.js'; +import { + PrivateKey as PrivateKeySnarky, + PublicKey as PublicKeySnarky, +} from '../../lib/provable/crypto/signature.js'; +import { Random, test, withHardCoded } from '../../lib/testing/property.js'; +import { PrivateKey, PublicKey } from './curve-bigint.js'; +import { hashWithPrefix, packToFields, prefixes } from './poseidon-bigint.js'; +import { Pickles, Test } from '../../snarky.js'; +import { Memo } from './memo.js'; +import { RandomTransaction } from './random-transaction.js'; import { accountUpdateFromFeePayer, accountUpdateHash, @@ -27,23 +36,11 @@ import { verifyZkappCommandSignature, } from './sign-zkapp-command.js'; import { - hashWithPrefix, - packToFields, - prefixes, -} from '../../provable/poseidon-bigint.js'; -import { packToFields as packToFieldsSnarky } from '../../lib/hash.js'; -import { Memo } from './memo.js'; -import { - NetworkId, Signature, signFieldElement, verifyFieldElement, } from './signature.js'; -import { Random, test, withHardCoded } from '../../lib/testing/property.js'; -import { RandomTransaction } from './random-transaction.js'; -import { Ml, MlHashInput } from '../../lib/ml/conversion.js'; -import { FieldConst } from '../../lib/field.js'; -import { mocks } from '../../bindings/crypto/constants.js'; +import { NetworkId } from './types.js'; // monkey-patch bigint to json (BigInt.prototype as any).toJSON = function () { @@ -62,7 +59,7 @@ test(Random.json.publicKey, (publicKeyBase58) => { }); // empty account update -let dummy = AccountUpdate.emptyValue(); +let dummy = AccountUpdate.empty(); let dummySnarky = AccountUpdateSnarky.dummy(); expect(AccountUpdate.toJSON(dummy)).toEqual( AccountUpdateSnarky.toJSON(dummySnarky) @@ -81,28 +78,46 @@ expect(stringify(dummyInput.packed)).toEqual( stringify(dummyInputSnarky.packed) ); -test(Random.accountUpdate, (accountUpdate) => { - fixVerificationKey(accountUpdate); +test( + Random.accountUpdate, + RandomTransaction.networkId, + (accountUpdate, networkId) => { + const minaInstance = Network({ + networkId, + mina: 'http://localhost:8080/graphql', + }); - // example account update - let accountUpdateJson: Json.AccountUpdate = - AccountUpdate.toJSON(accountUpdate); + fixVerificationKey(accountUpdate); - // account update hash - let accountUpdateSnarky = AccountUpdateSnarky.fromJSON(accountUpdateJson); - let inputSnarky = TypesSnarky.AccountUpdate.toInput(accountUpdateSnarky); - let input = AccountUpdate.toInput(accountUpdate); - expect(toJSON(input.fields)).toEqual(toJSON(inputSnarky.fields)); - expect(toJSON(input.packed)).toEqual(toJSON(inputSnarky.packed)); + // example account update + let accountUpdateJson: Json.AccountUpdate = + AccountUpdate.toJSON(accountUpdate); - let packed = packToFields(input); - let packedSnarky = packToFieldsSnarky(inputSnarky); - expect(toJSON(packed)).toEqual(toJSON(packedSnarky)); + // account update hash + let accountUpdateSnarky = AccountUpdateSnarky.fromJSON(accountUpdateJson); + let inputSnarky = TypesSnarky.AccountUpdate.toInput(accountUpdateSnarky); + let input = AccountUpdate.toInput(accountUpdate); + expect(toJSON(input.fields)).toEqual(toJSON(inputSnarky.fields)); + expect(toJSON(input.packed)).toEqual(toJSON(inputSnarky.packed)); - let hash = accountUpdateHash(accountUpdate); - let hashSnarky = accountUpdateSnarky.hash(); - expect(hash).toEqual(hashSnarky.toBigInt()); -}); + let packed = packToFields(input); + let packedSnarky = packToFieldsSnarky(inputSnarky); + expect(toJSON(packed)).toEqual(toJSON(packedSnarky)); + + setActiveInstance(minaInstance); + let hashSnarky = accountUpdateSnarky.hash(); + let hash = accountUpdateHash(accountUpdate, networkId); + expect(hash).toEqual(hashSnarky.toBigInt()); + + // check against different network hash + expect(hash).not.toEqual( + accountUpdateHash( + accountUpdate, + NetworkId.toString(networkId) === 'mainnet' ? 'testnet' : 'mainnet' + ) + ); + } +); // private key to/from base58 test(Random.json.privateKey, (feePayerKeyBase58) => { @@ -125,18 +140,25 @@ test(memoGenerator, (memoString) => { }); // zkapp transaction - basic properties & commitment -test(RandomTransaction.zkappCommand, (zkappCommand, assert) => { - zkappCommand.accountUpdates.forEach(fixVerificationKey); +test( + RandomTransaction.zkappCommand, + RandomTransaction.networkId, + (zkappCommand, networkId, assert) => { + zkappCommand.accountUpdates.forEach(fixVerificationKey); - assert(isCallDepthValid(zkappCommand)); - let zkappCommandJson = ZkappCommand.toJSON(zkappCommand); - let ocamlCommitments = Test.hashFromJson.transactionCommitments( - JSON.stringify(zkappCommandJson) - ); - let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); - let commitment = callForestHash(callForest); - expect(commitment).toEqual(FieldConst.toBigint(ocamlCommitments.commitment)); -}); + assert(isCallDepthValid(zkappCommand)); + let zkappCommandJson = ZkappCommand.toJSON(zkappCommand); + let ocamlCommitments = Test.hashFromJson.transactionCommitments( + JSON.stringify(zkappCommandJson), + NetworkId.toString(networkId) + ); + let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); + let commitment = callForestHash(callForest, networkId); + expect(commitment).toEqual( + FieldConst.toBigint(ocamlCommitments.commitment) + ); + } +); // invalid zkapp transactions test.negative( @@ -151,7 +173,9 @@ test.negative( // zkapp transaction test( RandomTransaction.zkappCommandAndFeePayerKey, - ({ feePayerKey, zkappCommand }) => { + RandomTransaction.networkId, + (zkappCommandAndFeePayerKey, networkId) => { + const { feePayerKey, zkappCommand } = zkappCommandAndFeePayerKey; zkappCommand.accountUpdates.forEach(fixVerificationKey); let feePayerKeyBase58 = PrivateKey.toBase58(feePayerKey); @@ -162,7 +186,7 @@ test( feePayer.authorization = Signature.toBase58(Signature.dummy()); let zkappCommandJson = ZkappCommand.toJSON(zkappCommand); - // snarkyjs fromJSON -> toJSON roundtrip, + consistency with mina-signer + // o1js fromJSON -> toJSON roundtrip, + consistency with mina-signer let zkappCommandSnarky = ZkappCommandSnarky.fromJSON(zkappCommandJson); let zkappCommandJsonSnarky = ZkappCommandSnarky.toJSON(zkappCommandSnarky); expect(JSON.stringify(zkappCommandJson)).toEqual( @@ -173,10 +197,11 @@ test( // tx commitment let ocamlCommitments = Test.hashFromJson.transactionCommitments( - JSON.stringify(zkappCommandJson) + JSON.stringify(zkappCommandJson), + NetworkId.toString(networkId) ); let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); - let commitment = callForestHash(callForest); + let commitment = callForestHash(callForest, networkId); expect(commitment).toEqual( FieldConst.toBigint(ocamlCommitments.commitment) ); @@ -200,7 +225,7 @@ test( stringify(feePayerInput1.packed) ); - let feePayerDigest = feePayerHash(feePayer); + let feePayerDigest = feePayerHash(feePayer, networkId); expect(feePayerDigest).toEqual( FieldConst.toBigint(ocamlCommitments.feePayerHash) ); @@ -215,55 +240,42 @@ test( ); // signature - let sigTestnet = signFieldElement(fullCommitment, feePayerKey, 'testnet'); - let sigMainnet = signFieldElement(fullCommitment, feePayerKey, 'mainnet'); - let sigTestnetOcaml = Test.signature.signFieldElement( - ocamlCommitments.fullCommitment, - Ml.fromPrivateKey(feePayerKeySnarky), - false + let sigFieldElements = signFieldElement( + fullCommitment, + feePayerKey, + networkId ); - let sigMainnetOcaml = Test.signature.signFieldElement( + let sigOCaml = Test.signature.signFieldElement( ocamlCommitments.fullCommitment, Ml.fromPrivateKey(feePayerKeySnarky), - true + NetworkId.toString(networkId) ); - expect(Signature.toBase58(sigTestnet)).toEqual(sigTestnetOcaml); - expect(Signature.toBase58(sigMainnet)).toEqual(sigMainnetOcaml); + + expect(Signature.toBase58(sigFieldElements)).toEqual(sigOCaml); let verify = (s: Signature, id: NetworkId) => verifyFieldElement(s, fullCommitment, feePayerAddress, id); - expect(verify(sigTestnet, 'testnet')).toEqual(true); - expect(verify(sigTestnet, 'mainnet')).toEqual(false); - expect(verify(sigMainnet, 'testnet')).toEqual(false); - expect(verify(sigMainnet, 'mainnet')).toEqual(true); + + expect(verify(sigFieldElements, networkId)).toEqual(true); + expect( + verify(sigFieldElements, networkId === 'mainnet' ? 'testnet' : 'mainnet') + ).toEqual(false); // full end-to-end test: sign a zkapp transaction - let sTest = signZkappCommand( - zkappCommandJson, - feePayerKeyBase58, - 'testnet' - ); - expect(sTest.feePayer.authorization).toEqual(sigTestnetOcaml); - let sMain = signZkappCommand( - zkappCommandJson, - feePayerKeyBase58, - 'mainnet' - ); - expect(sMain.feePayer.authorization).toEqual(sigMainnetOcaml); + let sig = signZkappCommand(zkappCommandJson, feePayerKeyBase58, networkId); + expect(sig.feePayer.authorization).toEqual(sigOCaml); let feePayerAddressBase58 = PublicKey.toBase58(feePayerAddress); expect( - verifyZkappCommandSignature(sTest, feePayerAddressBase58, 'testnet') + verifyZkappCommandSignature(sig, feePayerAddressBase58, networkId) ).toEqual(true); expect( - verifyZkappCommandSignature(sTest, feePayerAddressBase58, 'mainnet') + verifyZkappCommandSignature( + sig, + feePayerAddressBase58, + networkId === 'mainnet' ? 'testnet' : 'mainnet' + ) ).toEqual(false); - expect( - verifyZkappCommandSignature(sMain, feePayerAddressBase58, 'testnet') - ).toEqual(false); - expect( - verifyZkappCommandSignature(sMain, feePayerAddressBase58, 'mainnet') - ).toEqual(true); } ); @@ -275,7 +287,7 @@ function fixVerificationKey(a: AccountUpdate) { let [, data, hash] = Pickles.dummyVerificationKey(); a.body.update.verificationKey.value = { data, - hash: Field.fromBytes([...hash]), + hash: FieldConst.toBigint(hash), }; } else { a.body.update.verificationKey.value = { diff --git a/src/mina-signer/src/signature.ts b/src/mina-signer/src/signature.ts index 2731b7874e..bd3d384978 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -1,12 +1,12 @@ import { blake2b } from 'blakejs'; -import { Field } from '../../provable/field-bigint.js'; +import { Field } from './field-bigint.js'; import { Group, Scalar, PrivateKey, versionNumbers, PublicKey, -} from '../../provable/curve-bigint.js'; +} from './curve-bigint.js'; import { HashInput, hashWithPrefix, @@ -17,16 +17,17 @@ import { packToFieldsLegacy, inputToBitsLegacy, HashLegacy, -} from '../../provable/poseidon-bigint.js'; +} from './poseidon-bigint.js'; import { bitsToBytes, bytesToBits, record, withVersionNumber, } from '../../bindings/lib/binable.js'; -import { base58 } from '../../lib/base58.js'; +import { base58 } from '../../lib/util/base58.js'; import { versionBytes } from '../../bindings/crypto/constants.js'; -import { Pallas } from '../../bindings/crypto/elliptic_curve.js'; +import { Pallas } from '../../bindings/crypto/elliptic-curve.js'; +import { NetworkId } from './types.js'; export { sign, @@ -35,15 +36,16 @@ export { verifyFieldElement, Signature, SignatureJson, - NetworkId, signLegacy, verifyLegacy, deriveNonce, + signaturePrefix, + zkAppBodyPrefix, }; const networkIdMainnet = 0x01n; const networkIdTestnet = 0x00n; -type NetworkId = 'mainnet' | 'testnet'; + type Signature = { r: Field; s: Scalar }; type SignatureJson = { field: string; scalar: string }; @@ -150,10 +152,10 @@ function deriveNonce( ): Scalar { let { x, y } = publicKey; let d = Field(privateKey); - let id = networkId === 'mainnet' ? networkIdMainnet : networkIdTestnet; + let id = getNetworkIdHashInput(networkId); let input = HashInput.append(message, { fields: [x, y, d], - packed: [[id, 8]], + packed: [id], }); let packedInput = packToFields(input); let inputBits = packedInput.map(Field.toBits).flat(); @@ -189,11 +191,7 @@ function hashMessage( ): Scalar { let { x, y } = publicKey; let input = HashInput.append(message, { fields: [x, y, r] }); - let prefix = - networkId === 'mainnet' - ? prefixes.signatureMainnet - : prefixes.signatureTestnet; - return hashWithPrefix(prefix, packToFields(input)); + return hashWithPrefix(signaturePrefix(networkId), packToFields(input)); } /** @@ -280,7 +278,7 @@ function deriveNonceLegacy( ): Scalar { let { x, y } = publicKey; let scalarBits = Scalar.toBits(privateKey); - let id = networkId === 'mainnet' ? networkIdMainnet : networkIdTestnet; + let id = getNetworkIdHashInput(networkId)[0]; let idBits = bytesToBits([Number(id)]); let input = HashInputLegacy.append(message, { fields: [x, y], @@ -311,9 +309,68 @@ function hashMessageLegacy( ): Scalar { let { x, y } = publicKey; let input = HashInputLegacy.append(message, { fields: [x, y, r], bits: [] }); - let prefix = - networkId === 'mainnet' - ? prefixes.signatureMainnet - : prefixes.signatureTestnet; + let prefix = signaturePrefix(networkId); return HashLegacy.hashWithPrefix(prefix, packToFieldsLegacy(input)); } + +const numberToBytePadded = (b: number) => b.toString(2).padStart(8, '0'); + +function networkIdOfString(n: string): [bigint, number] { + let l = n.length; + let acc = ''; + for (let i = l - 1; i >= 0; i--) { + let b = n.charCodeAt(i); + let padded = numberToBytePadded(b); + acc = acc.concat(padded); + } + return [BigInt('0b' + acc), acc.length]; +} + +function getNetworkIdHashInput(network: NetworkId): [bigint, number] { + let s = NetworkId.toString(network); + switch (s) { + case 'mainnet': + return [networkIdMainnet, 8]; + case 'testnet': + return [networkIdTestnet, 8]; + default: + return networkIdOfString(s); + } +} + +const createCustomPrefix = (prefix: string) => { + const maxLength = 20; + const paddingChar = '*'; + let length = prefix.length; + + if (length <= maxLength) { + let diff = maxLength - length; + return prefix + paddingChar.repeat(diff); + } else { + return prefix.substring(0, maxLength); + } +}; + +const signaturePrefix = (network: NetworkId) => { + let s = NetworkId.toString(network); + switch (s) { + case 'mainnet': + return prefixes.signatureMainnet; + case 'testnet': + return prefixes.signatureTestnet; + default: + return createCustomPrefix(s + 'Signature'); + } +}; + +const zkAppBodyPrefix = (network: NetworkId) => { + let s = NetworkId.toString(network); + switch (s) { + case 'mainnet': + return prefixes.zkappBodyMainnet; + case 'testnet': + return prefixes.zkappBodyTestnet; + default: + return createCustomPrefix(s + 'ZkappBody'); + } +}; diff --git a/src/mina-signer/src/signature.unit-test.ts b/src/mina-signer/src/signature.unit-test.ts index 2fb520f02c..c26d2cd45b 100644 --- a/src/mina-signer/src/signature.unit-test.ts +++ b/src/mina-signer/src/signature.unit-test.ts @@ -7,42 +7,49 @@ import { verify, verifyFieldElement, } from './signature.js'; -import { Ledger, Test } from '../../snarky.js'; -import { Field as FieldSnarky } from '../../lib/core.js'; -import { Field } from '../../provable/field-bigint.js'; -import { PrivateKey, PublicKey } from '../../provable/curve-bigint.js'; -import { PrivateKey as PrivateKeySnarky } from '../../lib/signature.js'; -import { p } from '../../bindings/crypto/finite_field.js'; +import { Test } from '../../snarky.js'; +import { Field } from './field-bigint.js'; +import { PrivateKey, PublicKey } from './curve-bigint.js'; +import { PrivateKey as PrivateKeySnarky } from '../../lib/provable/crypto/signature.js'; +import { p } from '../../bindings/crypto/finite-field.js'; import { AccountUpdate } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; -import { HashInput } from '../../bindings/lib/provable-bigint.js'; +import { HashInput } from './derivers-bigint.js'; import { Ml } from '../../lib/ml/conversion.js'; -import { FieldConst } from '../../lib/field.js'; +import { FieldConst } from '../../lib/provable/core/fieldvar.js'; +import { NetworkId } from './types.js'; // check consistency with OCaml, where we expose the function to sign 1 field element with "testnet" function checkConsistentSingle( msg: Field, key: PrivateKey, keySnarky: PrivateKeySnarky, - pk: PublicKey + pk: PublicKey, + networkId: NetworkId ) { - let sigTest = signFieldElement(msg, key, 'testnet'); - let sigMain = signFieldElement(msg, key, 'mainnet'); + let sig = signFieldElement(msg, key, networkId); + // verify - let okTestnetTestnet = verifyFieldElement(sigTest, msg, pk, 'testnet'); - let okMainnetTestnet = verifyFieldElement(sigMain, msg, pk, 'testnet'); - let okTestnetMainnet = verifyFieldElement(sigTest, msg, pk, 'mainnet'); - let okMainnetMainnet = verifyFieldElement(sigMain, msg, pk, 'mainnet'); - expect(okTestnetTestnet).toEqual(true); - expect(okMainnetTestnet).toEqual(false); - expect(okTestnetMainnet).toEqual(false); - expect(okMainnetMainnet).toEqual(true); + expect(verifyFieldElement(sig, msg, pk, networkId)).toEqual(true); + + // verify against different network + expect( + verifyFieldElement( + sig, + msg, + pk, + networkId === 'mainnet' ? 'testnet' : 'mainnet' + ) + ).toEqual(false); + // consistent with OCaml let msgMl = FieldConst.fromBigint(msg); let keyMl = Ml.fromPrivateKey(keySnarky); - let actualTest = Test.signature.signFieldElement(msgMl, keyMl, false); - let actualMain = Test.signature.signFieldElement(msgMl, keyMl, true); - expect(Signature.toBase58(sigTest)).toEqual(actualTest); - expect(Signature.toBase58(sigMain)).toEqual(actualMain); + let actualTest = Test.signature.signFieldElement( + msgMl, + keyMl, + NetworkId.toString(networkId) + ); + expect(Signature.toBase58(sig)).toEqual(actualTest); } // check that various multi-field hash inputs can be verified @@ -96,12 +103,16 @@ for (let i = 0; i < 10; i++) { // hard coded single field elements let hardcoded = [0n, 1n, 2n, p - 1n]; for (let x of hardcoded) { - checkConsistentSingle(x, key, keySnarky, publicKey); + checkConsistentSingle(x, key, keySnarky, publicKey, 'testnet'); + checkConsistentSingle(x, key, keySnarky, publicKey, 'mainnet'); + checkConsistentSingle(x, key, keySnarky, publicKey, { custom: 'other' }); } // random single field elements for (let i = 0; i < 10; i++) { let x = randomFields[i]; - checkConsistentSingle(x, key, keySnarky, publicKey); + checkConsistentSingle(x, key, keySnarky, publicKey, 'testnet'); + checkConsistentSingle(x, key, keySnarky, publicKey, 'mainnet'); + checkConsistentSingle(x, key, keySnarky, publicKey, { custom: 'other' }); } // hard-coded multi-element hash inputs let messages: HashInput[] = [ @@ -122,7 +133,7 @@ for (let i = 0; i < 10; i++) { [0xffff_ffff_ffff_ffffn, 64], ], }, - AccountUpdate.toInput(AccountUpdate.emptyValue()), + AccountUpdate.toInput(AccountUpdate.empty()), ]; for (let msg of messages) { checkCanVerify(msg, key, publicKey); diff --git a/src/mina-signer/src/test-vectors/legacySignatures.ts b/src/mina-signer/src/test-vectors/legacySignatures.ts index d31f538f58..7090587475 100644 --- a/src/mina-signer/src/test-vectors/legacySignatures.ts +++ b/src/mina-signer/src/test-vectors/legacySignatures.ts @@ -90,7 +90,7 @@ let strings = [ * - the 3 stake delegations, * - the 3 strings. */ -let signatures = { +let signatures: { [k: string]: { field: string; scalar: string }[] } = { testnet: [ { field: diff --git a/src/mina-signer/src/transaction-hash.ts b/src/mina-signer/src/transaction-hash.ts index 7a8e56ef6b..1bc923082a 100644 --- a/src/mina-signer/src/transaction-hash.ts +++ b/src/mina-signer/src/transaction-hash.ts @@ -1,4 +1,4 @@ -import { Bool, Field, UInt32, UInt64 } from '../../provable/field-bigint.js'; +import { Bool, Field, UInt32, UInt64 } from './field-bigint.js'; import { Binable, BinableString, @@ -21,10 +21,10 @@ import { delegationFromJson, paymentFromJson, } from './sign-legacy.js'; -import { PublicKey, Scalar } from '../../provable/curve-bigint.js'; +import { PublicKey, Scalar } from './curve-bigint.js'; import { Signature, SignatureJson } from './signature.js'; import { blake2b } from 'blakejs'; -import { base58, withBase58 } from '../../lib/base58.js'; +import { base58, withBase58 } from '../../lib/util/base58.js'; import { versionBytes } from '../../bindings/crypto/constants.js'; export { @@ -82,7 +82,10 @@ function userCommandToEnum({ common, body }: UserCommand): UserCommandEnum { let { tag: type, ...value } = body; switch (type) { case 'Payment': - return { common, body: { type, value: { receiver: body.receiver, amount: body.amount } } }; + return { + common, + body: { type, value: { receiver: body.receiver, amount: body.amount } }, + }; case 'StakeDelegation': let { receiver: newDelegate } = value; return { @@ -120,10 +123,9 @@ const Payment = record( }, ['receiver', 'amount'] ); -const Delegation = record( - { newDelegate: BinablePublicKey }, - ['newDelegate'] -); +const Delegation = record({ newDelegate: BinablePublicKey }, [ + 'newDelegate', +]); type DelegationEnum = { type: 'SetDelegate'; value: Delegation }; const DelegationEnum = enumWithArgument<[DelegationEnum]>([ { type: 'SetDelegate', value: Delegation }, @@ -254,7 +256,7 @@ const CommonV1 = with1( ) ) ); -type PaymentV1 = Payment & { source: PublicKey, tokenId: UInt64 }; +type PaymentV1 = Payment & { source: PublicKey; tokenId: UInt64 }; const PaymentV1 = with1( with1( record( diff --git a/src/mina-signer/src/transaction-hash.unit-test.ts b/src/mina-signer/src/transaction-hash.unit-test.ts index 955f451cd2..eec695f646 100644 --- a/src/mina-signer/src/transaction-hash.unit-test.ts +++ b/src/mina-signer/src/transaction-hash.unit-test.ts @@ -20,7 +20,7 @@ import { delegationFromJson, } from './sign-legacy.js'; import { Signature, SignatureJson } from './signature.js'; -import { PublicKey } from '../../provable/curve-bigint.js'; +import { PublicKey } from './curve-bigint.js'; import { Memo } from './memo.js'; import { expect } from 'expect'; import { versionBytes } from '../../bindings/crypto/constants.js'; @@ -180,7 +180,12 @@ function paymentToOcamlV1({ common: commonToOcamlV1(common), body: [ 'Payment', - { source_pk: common.feePayer, receiver_pk: receiver, amount, token_id: '1' }, + { + source_pk: common.feePayer, + receiver_pk: receiver, + amount, + token_id: '1', + }, ], }, signer: common.feePayer, @@ -220,7 +225,10 @@ function delegationToOcamlV1({ common: commonToOcamlV1(common), body: [ 'Stake_delegation', - ['Set_delegate', { delegator: common.feePayer, new_delegate: newDelegate }], + [ + 'Set_delegate', + { delegator: common.feePayer, new_delegate: newDelegate }, + ], ], }, signer: common.feePayer, diff --git a/src/mina-signer/src/TSTypes.ts b/src/mina-signer/src/types.ts similarity index 90% rename from src/mina-signer/src/TSTypes.ts rename to src/mina-signer/src/types.ts index 732b85845d..87739e059b 100644 --- a/src/mina-signer/src/TSTypes.ts +++ b/src/mina-signer/src/types.ts @@ -9,7 +9,13 @@ export type Field = number | bigint | string; export type PublicKey = string; export type PrivateKey = string; export type Signature = SignatureJson; -export type Network = 'mainnet' | 'testnet'; +export type NetworkId = 'mainnet' | 'testnet' | { custom: string }; + +export const NetworkId = { + toString(network: NetworkId) { + return typeof network === 'string' ? network : network.custom; + }, +}; export type Keypair = { readonly privateKey: PrivateKey; diff --git a/src/mina-signer/src/Utils.ts b/src/mina-signer/src/utils.ts similarity index 98% rename from src/mina-signer/src/Utils.ts rename to src/mina-signer/src/utils.ts index 213246b81a..871c8c8081 100644 --- a/src/mina-signer/src/Utils.ts +++ b/src/mina-signer/src/utils.ts @@ -7,7 +7,7 @@ import type { SignedAny, SignedLegacy, SignableData, -} from './TSTypes.js'; +} from './types.js'; function hasCommonProperties(data: SignableData | ZkappCommand) { return ( diff --git a/src/mina-signer/tests/client.test.ts b/src/mina-signer/tests/client.test.ts index ca0cce3934..4ca384a2d2 100644 --- a/src/mina-signer/tests/client.test.ts +++ b/src/mina-signer/tests/client.test.ts @@ -1,4 +1,4 @@ -import Client from '../dist/node/mina-signer/MinaSigner.js'; +import Client from '../dist/node/mina-signer/mina-signer.js'; describe('Client Class Initialization', () => { let client; diff --git a/src/mina-signer/tests/keypair.test.ts b/src/mina-signer/tests/keypair.test.ts index 5e3e48dd59..2c19c95caa 100644 --- a/src/mina-signer/tests/keypair.test.ts +++ b/src/mina-signer/tests/keypair.test.ts @@ -1,4 +1,4 @@ -import Client from '../dist/node/mina-signer/MinaSigner.js'; +import Client from '../dist/node/mina-signer/mina-signer.js'; describe('Keypair', () => { let client: Client; diff --git a/src/mina-signer/tests/message.test.ts b/src/mina-signer/tests/message.test.ts index 26011eabff..bd9586fb19 100644 --- a/src/mina-signer/tests/message.test.ts +++ b/src/mina-signer/tests/message.test.ts @@ -1,5 +1,5 @@ -import Client from '../dist/node/mina-signer/MinaSigner.js'; -import type { PrivateKey } from '../dist/node/mina-signer/src/TSTypes.js'; +import Client from '../dist/node/mina-signer/mina-signer.js'; +import type { PrivateKey } from '../dist/node/mina-signer/src/types.js'; describe('Message', () => { describe('Mainnet network', () => { diff --git a/src/mina-signer/tests/payment.test.ts b/src/mina-signer/tests/payment.test.ts index 9a7d52804d..de84939d67 100644 --- a/src/mina-signer/tests/payment.test.ts +++ b/src/mina-signer/tests/payment.test.ts @@ -1,5 +1,5 @@ -import Client from '../dist/node/mina-signer/MinaSigner.js'; -import type { Keypair } from '../dist/node/mina-signer/src/TSTypes.js'; +import Client from '../dist/node/mina-signer/mina-signer.js'; +import type { Keypair } from '../dist/node/mina-signer/src/types.js'; describe('Payment', () => { describe('Mainnet network', () => { diff --git a/src/mina-signer/tests/rosetta.test.ts b/src/mina-signer/tests/rosetta.test.ts index 5db94f21e1..1981b135f3 100644 --- a/src/mina-signer/tests/rosetta.test.ts +++ b/src/mina-signer/tests/rosetta.test.ts @@ -1,4 +1,4 @@ -import Client from '../dist/node/mina-signer/MinaSigner.js'; +import Client from '../dist/node/mina-signer/mina-signer.js'; describe('Rosetta', () => { let client: Client; diff --git a/src/mina-signer/tests/stake-delegation.test.ts b/src/mina-signer/tests/stake-delegation.test.ts index ea59458e1f..16cdf0d8f6 100644 --- a/src/mina-signer/tests/stake-delegation.test.ts +++ b/src/mina-signer/tests/stake-delegation.test.ts @@ -1,5 +1,5 @@ -import Client from '../dist/node/mina-signer/MinaSigner.js'; -import type { Keypair } from '../dist/node/mina-signer/src/TSTypes.js'; +import Client from '../dist/node/mina-signer/mina-signer.js'; +import type { Keypair } from '../dist/node/mina-signer/src/types.js'; describe('Stake Delegation', () => { describe('Mainnet network', () => { diff --git a/src/mina-signer/tests/verify-in-snark.unit-test.ts b/src/mina-signer/tests/verify-in-snark.unit-test.ts index 6d04ab77d9..5ae1559006 100644 --- a/src/mina-signer/tests/verify-in-snark.unit-test.ts +++ b/src/mina-signer/tests/verify-in-snark.unit-test.ts @@ -1,9 +1,9 @@ -import { Field } from '../../lib/core.js'; -import { ZkProgram } from '../../lib/proof_system.js'; -import Client from '../MinaSigner.js'; -import { PrivateKey, Signature } from '../../lib/signature.js'; +import { Field } from '../../lib/provable/wrapped.js'; +import { ZkProgram } from '../../lib/proof-system/zkprogram.js'; +import Client from '../mina-signer.js'; +import { PrivateKey, Signature } from '../../lib/provable/crypto/signature.js'; import { expect } from 'expect'; -import { Provable } from '../../lib/provable.js'; +import { Provable } from '../../lib/provable/provable.js'; let fields = [10n, 20n, 30n, 340817401n, 2091283n, 1n, 0n]; let privateKey = 'EKENaWFuAiqktsnWmxq8zaoR8bSgVdscsghJE5tV6hPoNm8qBKWM'; @@ -16,26 +16,27 @@ let signed = client.signFields(fields, privateKey); let ok = client.verifyFields(signed); expect(ok).toEqual(true); -// sign with snarkyjs and check that we get the same signature +// sign with o1js and check that we get the same signature let fieldsSnarky = fields.map(Field); let privateKeySnarky = PrivateKey.fromBase58(privateKey); let signatureSnarky = Signature.create(privateKeySnarky, fieldsSnarky); expect(signatureSnarky.toBase58()).toEqual(signed.signature); -// verify out-of-snark with snarkyjs +// verify out-of-snark with o1js let publicKey = privateKeySnarky.toPublicKey(); let signature = Signature.fromBase58(signed.signature); Provable.assertEqual(Signature, signature, signatureSnarky); signature.verify(publicKey, fieldsSnarky).assertTrue(); -// verify in-snark with snarkyjs +// verify in-snark with o1js const Message = Provable.Array(Field, fields.length); const MyProgram = ZkProgram({ + name: 'verify-signature', methods: { verifySignature: { privateInputs: [Signature, Message], - method(signature: Signature, message: Field[]) { + async method(signature: Signature, message: Field[]) { signature.verify(publicKey, message).assertTrue(); }, }, diff --git a/src/mina-signer/tests/zkapp.unit-test.ts b/src/mina-signer/tests/zkapp.unit-test.ts index bf6c7073bd..1b3ffb66c3 100644 --- a/src/mina-signer/tests/zkapp.unit-test.ts +++ b/src/mina-signer/tests/zkapp.unit-test.ts @@ -1,17 +1,17 @@ import { ZkappCommand } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; import * as TransactionJson from '../../bindings/mina-transaction/gen/transaction-json.js'; -import Client from '../MinaSigner.js'; +import Client from '../mina-signer.js'; import { accountUpdateExample } from '../src/test-vectors/accountUpdate.js'; import { expect } from 'expect'; -import { Transaction } from '../../lib/mina.js'; -import { PrivateKey } from '../../lib/signature.js'; +import { Transaction } from '../../lib/mina/mina.js'; +import { PrivateKey } from '../../lib/provable/crypto/signature.js'; import { Signature } from '../src/signature.js'; import { mocks } from '../../bindings/crypto/constants.js'; const client = new Client({ network: 'testnet' }); let { publicKey, privateKey } = client.genKeys(); -let dummy = ZkappCommand.toJSON(ZkappCommand.emptyValue()); +let dummy = ZkappCommand.toJSON(ZkappCommand.empty()); let dummySignature = Signature.toBase58(Signature.dummy()); // we construct a transaction which needs signing of the fee payer and another account update @@ -84,7 +84,7 @@ expect( client.getAccountUpdateMinimumFee(exampleZkappCommand.accountUpdates) ).toBe(0.002); -// same transaction signed with snarkyjs (OCaml implementation) gives the same result +// same transaction signed with o1js (OCaml implementation) gives the same result let transactionJson = { ...exampleZkappCommand, diff --git a/src/snarky.d.ts b/src/snarky.d.ts index e23ce0e918..e8f72dca55 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -1,140 +1,57 @@ import type { Account as JsonAccount } from './bindings/mina-transaction/gen/transaction-json.js'; -import type { Field, FieldConst, FieldVar } from './lib/field.js'; -import type { BoolVar, Bool } from './lib/bool.js'; -import type { ScalarConst } from './lib/scalar.js'; +import type { Field } from './lib/provable/field.js'; +import type { + FieldVar, + FieldConst, + VarFieldVar, +} from './lib/provable/core/fieldvar.ts'; +import type { BoolVar } from './lib/provable/bool.ts'; +import type { ScalarConst } from './lib/provable/scalar.js'; import type { MlArray, - MlTuple, + MlPair, MlList, MlOption, MlBool, MlBytes, + MlResult, + MlUnit, + MlString, + MlTuple, } from './lib/ml/base.js'; import type { MlHashInput } from './lib/ml/conversion.js'; +import type { + SnarkKey, + SnarkKeyHeader, + MlWrapVerificationKey, +} from './lib/proof-system/prover-keys.js'; +import type { + WasmFpSrs, + WasmFqSrs, +} from './bindings/compiled/node_bindings/plonk_wasm.cjs'; +import * as wasm from './bindings/compiled/node_bindings/plonk_wasm.cjs'; +import type { KimchiGateType } from './lib/provable/gates.ts'; +import type { MlConstraintSystem } from './lib/provable/core/provable-context.ts'; +import type { FieldVector } from './bindings/crypto/bindings/vector.ts'; -export { ProvablePure, Provable, Ledger, Pickles, Gate }; +export { Ledger, Pickles, Gate, GateType, wasm }; // internal -export { Snarky, Test, JsonGate, MlPublicKey, MlPublicKeyVar }; - -/** - * `Provable` is the general circuit type interface in SnarkyJS. `Provable` interface describes how a type `T` is made up of {@link Field} elements and "auxiliary" (non-provable) data. - * - * `Provable` is the required input type in a few places in SnarkyJS. One convenient way to create a `Provable` is using `Struct`. - * - * The properties and methods on the provable type exist in all base SnarkyJS types as well (aka. {@link Field}, {@link Bool}, etc.). In most cases, a zkApp developer does not need these functions to create zkApps. - */ -declare interface Provable { - /** - * A function that takes `value`, an element of type `T`, as argument and returns an array of {@link Field} elements that make up the provable data of `value`. - * - * @param value - the element of type `T` to generate the {@link Field} array from. - * - * @return A {@link Field} array describing how this `T` element is made up of {@link Field} elements. - */ - toFields: (value: T) => Field[]; - - /** - * A function that takes `value` (optional), an element of type `T`, as argument and returns an array of any type that make up the "auxiliary" (non-provable) data of `value`. - * - * @param value - the element of type `T` to generate the auxiliary data array from, optional. If not provided, a default value for auxiliary data is returned. - * - * @return An array of any type describing how this `T` element is made up of "auxiliary" (non-provable) data. - */ - toAuxiliary: (value?: T) => any[]; - - /** - * A function that returns an element of type `T` from the given provable and "auxiliary" data. - * - * **Important**: For any element of type `T`, this function is the reverse operation of calling {@link toFields} and {@link toAuxilary} methods on an element of type `T`. - * - * @param fields - an array of {@link Field} elements describing the provable data of the new `T` element. - * @param aux - an array of any type describing the "auxiliary" data of the new `T` element, optional. - * - * @return An element of type `T` generated from the given provable and "auxiliary" data. - */ - fromFields: (fields: Field[], aux: any[]) => T; - - /** - * Return the size of the `T` type in terms of {@link Field} type, as {@link Field} is the primitive type. - * - * **Warning**: This function returns a `number`, so you cannot use it to prove something on chain. You can use it during debugging or to understand the memory complexity of some type. - * - * @return A `number` representing the size of the `T` type in terms of {@link Field} type. - */ - sizeInFields(): number; - - /** - * Add assertions to the proof to check if `value` is a valid member of type `T`. - * This function does not return anything, instead it creates any number of assertions to prove that `value` is a valid member of the type `T`. - * - * For instance, calling check function on the type {@link Bool} asserts that the value of the element is either 1 or 0. - * - * @param value - the element of type `T` to put assertions on. - */ - check: (value: T) => void; -} - -/** - * `ProvablePure` is a special kind of {@link Provable} interface, where the "auxiliary" (non-provable) data is empty. This means the type consists only of field elements, in that sense it is "pure". - * Any element on the interface `ProvablePure` is also an element of the interface `Provable` where the "auxiliary" data is empty. - * - * Examples where `ProvablePure` is required are types of on-chain state, events and actions. - * - * It includes the same properties and methods as the {@link Provable} interface. - */ -declare interface ProvablePure extends Provable { - /** - * A function that takes `value`, an element of type `T`, as argument and returns an array of {@link Field} elements that make up the provable data of `value`. - * - * @param value - the element of type `T` to generate the {@link Field} array from. - * - * @return A {@link Field} array describing how this `T` element is made up of {@link Field} elements. - */ - toFields: (value: T) => Field[]; - - /** - * A function that takes `value` (optional), an element of type `T`, as argument and returns an array of any type that make up the "auxiliary" (non-provable) data of `value`. - * As any element of the interface `ProvablePure` includes no "auxiliary" data by definition, this function always returns a default value. - * - * @param value - the element of type `T` to generate the auxiliary data array from, optional. If not provided, a default value for auxiliary data is returned. - * - * @return An empty array, as any element of the interface `ProvablePure` includes no "auxiliary" data by definition. - */ - toAuxiliary: (value?: T) => any[]; - - /** - * A function that returns an element of type `T` from the given provable data. - * - * **Important**: For any element of type `T`, this function is the reverse operation of calling {@link toFields} method on an element of type `T`. - * - * @param fields - an array of {@link Field} elements describing the provable data of the new `T` element. - * - * @return An element of type `T` generated from the given provable data. - */ - fromFields: (fields: Field[]) => T; +export { + Snarky, + Test, + WasmModule, + withThreadPool, + JsonGate, + MlPublicKey, + MlPublicKeyVar, + FeatureFlags, + MlFeatureFlags, +}; - /** - * Return the size of the `T` type in terms of {@link Field} type, as {@link Field} is the primitive type. - * - * **Warning**: This function returns a `number`, so you cannot use it to prove something on chain. You can use it during debugging or to understand the memory complexity of some type. - * - * @return A `number` representing the size of the `T` type in terms of {@link Field} type. - */ - sizeInFields(): number; +type WasmModule = typeof wasm; - /** - * Add assertions to the proof to check if `value` is a valid member of type `T`. - * This function does not return anything, rather creates any number of assertions on the proof to prove `value` is a valid member of the type `T`. - * - * For instance, calling check function on the type {@link Bool} asserts that the value of the element is either 1 or 0. - * - * @param value - the element of type `T` to put assertions on. - */ - check: (value: T) => void; -} - -type MlGroup = MlTuple; +type MlGroup = MlPair; declare namespace Snarky { type Main = (publicInput: MlArray) => void; @@ -149,25 +66,14 @@ declare namespace Snarky { * Note for devs: This module is intended to closely mirror snarky-ml's core, low-level APIs. */ declare const Snarky: { - /** - * witness `sizeInFields` field element variables - * - * Note: this is called "exists" because in a proof, you use it like this: - * > "I prove that there exists x, such that (some statement)" - */ - exists( - sizeInFields: number, - compute: () => MlArray - ): MlArray; - /** - * witness a single field element variable - */ - existsVar(compute: () => FieldConst): FieldVar; - /** * APIs that have to do with running provable code */ run: { + /** + * Checks whether Snarky runs in "prover mode", that is, with witnesses + */ + inProver(): MlBool; /** * Runs code as a prover. */ @@ -177,39 +83,64 @@ declare const Snarky: { */ inProverBlock(): boolean; /** - * Runs code and checks its correctness. + * Setting that controls whether snarky throws an exception on violated constraint. */ - runAndCheck(f: () => void): void; + setEvalConstraints(value: MlBool): void; + /** + * Starts constraint system runner and returns a function to finish it. + */ + enterConstraintSystem(): () => MlConstraintSystem; + /** + * Starts witness generation and returns a function to finish it. + */ + enterGenerateWitness(): () => [ + _: 0, + public_inputs: FieldVector, + auxiliary_inputs: FieldVector + ]; /** - * Runs code in prover mode, without checking correctness. + * Starts an asProver / witness block and returns a function to finish it. */ - runUnchecked(f: () => void): void; + enterAsProver( + size: number + ): (fields: MlOption>) => MlArray; + /** - * Returns information about the constraint system in the callback function. + * Operations on snarky's internal state */ - constraintSystem(f: () => void): { - rows: number; - digest: string; - json: JsonConstraintSystem; + state: { + allocVar(state: SnarkyState): FieldVar; + storeFieldElt(state: SnarkyState, x: FieldConst): FieldVar; + getVariableValue(state: SnarkyState, x: FieldVar): FieldConst; + + asProver(state: SnarkyState): MlBool; + setAsProver(state: SnarkyState, value: MlBool): void; + hasWitness(state: SnarkyState): MlBool; }; }; /** - * APIs to add constraints on field variables + * APIs to interact with a `Backend.R1CS_constraint_system.t` */ - field: { + constraintSystem: { /** - * add x, y to get a new AST node Add(x, y); handles if x, y are constants + * Returns the number of rows of the constraint system. */ - add(x: FieldVar, y: FieldVar): FieldVar; + rows(system: MlConstraintSystem): number; /** - * scale x by a constant to get a new AST node Scale(c, x); handles if x is a constant + * Returns an md5 digest of the constraint system. */ - scale(c: FieldConst, x: FieldVar): FieldVar; + digest(system: MlConstraintSystem): string; /** - * witnesses z = x*y and constrains it with [assert_r1cs]; handles constants + * Returns a JSON representation of the constraint system. */ - mul(x: FieldVar, y: FieldVar): FieldVar; + toJson(system: MlConstraintSystem): JsonConstraintSystem; + }; + + /** + * APIs to add constraints on field variables + */ + field: { /** * evaluates a CVar by walking the AST and reading Vars from a list of public input + aux values */ @@ -230,22 +161,6 @@ declare const Snarky: { * x*x === x without handling of constants */ assertBoolean(x: FieldVar): void; - /** - * check x < y and x <= y - */ - compare( - bitLength: number, - x: FieldVar, - y: FieldVar - ): [_: 0, less: BoolVar, lessOrEqual: BoolVar]; - /** - * - */ - toBits(length: number, x: FieldVar): MlArray; - /** - * - */ - fromBits(bits: MlArray): FieldVar; /** * returns x truncated to the lowest `16 * lengthDiv16` bits * => can be used to assert that x fits in `16 * lengthDiv16` bits. @@ -254,42 +169,28 @@ declare const Snarky: { * does 16 bits per row (vs 1 bits per row that you can do with generic gates). */ truncateToBits16(lengthDiv16: number, x: FieldVar): FieldVar; - /** - * returns a new witness from an AST - * (implemented with toConstantAndTerms) - */ - seal(x: FieldVar): FieldVar; - /** - * Unfolds AST to get `x = c + c0*Var(i0) + ... + cn*Var(in)`, - * returns `(c, [(c0, i0), ..., (cn, in)])`; - * c is optional - */ - toConstantAndTerms( - x: FieldVar - ): [ - _: 0, - constant: MlOption, - terms: MlList> - ]; }; - bool: { - not(x: BoolVar): BoolVar; - - and(x: BoolVar, y: BoolVar): BoolVar; - - or(x: BoolVar, y: BoolVar): BoolVar; + gates: { + zero(in1: FieldVar, in2: FieldVar, out: FieldVar): void; - equals(x: BoolVar, y: BoolVar): BoolVar; + generic( + sl: FieldConst, + l: FieldVar, + sr: FieldConst, + r: FieldVar, + so: FieldConst, + o: FieldVar, + sm: FieldConst, + sc: FieldConst + ): void; - assertEqual(x: BoolVar, y: BoolVar): void; - }; + poseidon(state: MlArray>): void; - group: { /** * Low-level Elliptic Curve Addition gate. */ - ecadd( + ecAdd( p1: MlGroup, p2: MlGroup, p3: MlGroup, @@ -300,6 +201,152 @@ declare const Snarky: { x21_inv: FieldVar ): MlGroup; + ecScale( + state: MlArray< + [ + _: 0, + accs: MlArray>, + bits: MlArray, + ss: MlArray, + base: MlGroup, + nPrev: Field, + nNext: Field + ] + > + ): void; + + ecEndoscale( + state: MlArray< + [ + _: 0, + xt: FieldVar, + yt: FieldVar, + xp: FieldVar, + yp: FieldVar, + nAcc: FieldVar, + xr: FieldVar, + yr: FieldVar, + s1: FieldVar, + s3: FieldVar, + b1: FieldVar, + b2: FieldVar, + b3: FieldVar, + b4: FieldVar + ] + >, + xs: FieldVar, + ys: FieldVar, + nAcc: FieldVar + ): void; + + ecEndoscalar( + state: MlArray< + [ + _: 0, + n0: FieldVar, + n8: FieldVar, + a0: FieldVar, + b0: FieldVar, + a8: FieldVar, + b8: FieldVar, + x0: FieldVar, + x1: FieldVar, + x2: FieldVar, + x3: FieldVar, + x4: FieldVar, + x5: FieldVar, + x6: FieldVar, + x7: FieldVar + ] + > + ): void; + + lookup(input: MlTuple): void; + + /** + * Range check gate + * + * @param v0 field var to be range checked + * @param v0p bits 16 to 88 as 6 12-bit limbs + * @param v0c bits 0 to 16 as 8 2-bit limbs + * @param compact boolean field elements -- whether to use "compact mode" + */ + rangeCheck0( + v0: FieldVar, + v0p: MlTuple, + v0c: MlTuple, + compact: FieldConst + ): void; + + rangeCheck1( + v2: FieldVar, + v12: FieldVar, + vCurr: MlTuple, + vNext: MlTuple + ): void; + + xor( + in1: FieldVar, + in2: FieldVar, + out: FieldVar, + in1_0: FieldVar, + in1_1: FieldVar, + in1_2: FieldVar, + in1_3: FieldVar, + in2_0: FieldVar, + in2_1: FieldVar, + in2_2: FieldVar, + in2_3: FieldVar, + out_0: FieldVar, + out_1: FieldVar, + out_2: FieldVar, + out_3: FieldVar + ): void; + + foreignFieldAdd( + left: MlTuple, + right: MlTuple, + fieldOverflow: FieldVar, + carry: FieldVar, + foreignFieldModulus: MlTuple, + sign: FieldConst + ): void; + + foreignFieldMul( + left: MlTuple, + right: MlTuple, + remainder: MlTuple, + quotient: MlTuple, + quotientHiBound: FieldVar, + product1: MlTuple, + carry0: FieldVar, + carry1p: MlTuple, + carry1c: MlTuple, + foreignFieldModulus2: FieldConst, + negForeignFieldModulus: MlTuple + ): void; + + rotate( + field: FieldVar, + rotated: FieldVar, + excess: FieldVar, + limbs: MlArray, + crumbs: MlArray, + two_to_rot: FieldConst + ): void; + + addFixedLookupTable(id: number, data: MlArray>): void; + + addRuntimeTableConfig(id: number, firstColumn: MlArray): void; + + raw( + kind: KimchiGateType, + values: MlArray, + coefficients: MlArray + ): void; + }; + + group: { scale(p: MlGroup, s: MlArray): MlGroup; }; @@ -341,13 +388,14 @@ declare const Snarky: { }; }; + // TODO: implement in TS poseidon: { update( state: MlArray, input: MlArray ): [0, FieldVar, FieldVar, FieldVar]; - hashToGroup(input: MlArray): MlTuple; + hashToGroup(input: MlArray): MlPair; sponge: { create(isChecked: boolean): unknown; @@ -357,15 +405,52 @@ declare const Snarky: { }; }; +type MlRef = [_: 0, contents: T]; + +type SnarkyVector = [0, [unknown, number, FieldVector]]; +type ConstraintSystem = unknown; + +type SnarkyState = [ + _: 0, + system: MlOption, + input: SnarkyVector, + aux: SnarkyVector, + eval_constraints: MlBool, + num_inputs: number, + next_auxiliary: MlRef, + has_witness: MlBool, + stack: MlList, + handler: unknown, + is_running: MlBool, + as_prover: MlRef, + log_constraint: unknown +]; + +type GateType = + | 'Zero' + | 'Generic' + | 'Poseidon' + | 'CompleteAdd' + | 'VarbaseMul' + | 'EndoMul' + | 'EndoMulScalar' + | 'Lookup' + | 'RangeCheck0' + | 'RangeCheck1' + | 'ForeignFieldAdd' + | 'ForeignFieldMul' + | 'Xor16' + | 'Rot64'; + type JsonGate = { - typ: string; + typ: GateType; wires: { row: number; col: number }[]; - coeffs: number[][]; + coeffs: string[]; }; type JsonConstraintSystem = { gates: JsonGate[]; public_input_size: number }; type Gate = { - type: string; + type: GateType; wires: { row: number; col: number }[]; coeffs: string[]; }; @@ -436,7 +521,7 @@ declare const Test: { }; poseidon: { - hashToGroup(input: MlArray): MlTuple; + hashToGroup(input: MlArray): MlPair; }; signature: { @@ -446,7 +531,7 @@ declare const Test: { signFieldElement( messageHash: FieldConst, privateKey: ScalarConst, - isMainnet: boolean + networkId: string ): string; /** * Returns a dummy signature. @@ -458,11 +543,14 @@ declare const Test: { accountUpdate(json: string): MlArray; }; hashFromJson: { - accountUpdate(json: string): FieldConst; + accountUpdate(json: string, networkId: string): FieldConst; /** * Returns the commitment of a JSON transaction. */ - transactionCommitments(txJson: string): { + transactionCommitments( + txJson: string, + networkId: string + ): { commitment: FieldConst; fullCommitment: FieldConst; feePayerHash: FieldConst; @@ -492,21 +580,74 @@ declare const Test: { serializeCommon(common: string): { data: Uint8Array }; hashPayment(payment: string): string; hashPaymentV1(payment: string): string; + hashZkAppCommand(command: string): string; }; }; +type FeatureFlags = { + rangeCheck0: boolean; + rangeCheck1: boolean; + foreignFieldAdd: boolean; + foreignFieldMul: boolean; + xor: boolean; + rot: boolean; + lookup: boolean; + runtimeTables: boolean; +}; + +type MlFeatureFlags = [ + _: 0, + rangeCheck0: MlBool, + rangeCheck1: MlBool, + foreignFieldAdd: MlBool, + foreignFieldMul: MlBool, + xor: MlBool, + rot: MlBool, + lookup: MlBool, + runtimeTables: MlBool +]; + declare namespace Pickles { type Proof = unknown; // opaque to js type Statement = [_: 0, publicInput: MlArray, publicOutput: MlArray]; + + /** + * A "rule" is a circuit plus some metadata for `Pickles.compile` + */ type Rule = { identifier: string; - main: (publicInput: MlArray) => { + /** + * The main circuit functions + */ + main: (publicInput: MlArray) => Promise<{ publicOutput: MlArray; previousStatements: MlArray>; shouldVerify: MlArray; - }; + }>; + /** + * Feature flags which enable certain custom gates + */ + featureFlags: MlFeatureFlags; + /** + * Description of previous proofs to verify in this rule + */ proofsToVerify: MlArray<{ isSelf: true } | { isSelf: false; tag: unknown }>; }; + + /** + * Type to configure how Pickles should cache prover keys + */ + type Cache = [ + _: 0, + read: (header: SnarkKeyHeader, path: string) => MlResult, + write: ( + header: SnarkKeyHeader, + value: SnarkKey, + path: string + ) => MlResult, + canWrite: MlBool + ]; + type Prover = ( publicInput: MlArray, previousProofs: MlArray @@ -537,9 +678,10 @@ declare const Pickles: { */ compile: ( rules: MlArray, - signature: { + config: { publicInputSize: number; publicOutputSize: number; + storable?: Pickles.Cache; overrideWrapDomain?: 0 | 1 | 2; } ) => { @@ -552,7 +694,7 @@ declare const Pickles: { /** * @returns (base64 vk, hash) */ - getVerificationKey: () => [_: 0, data: string, hash: FieldConst]; + getVerificationKey: () => Promise<[_: 0, data: string, hash: FieldConst]>; }; verify( @@ -561,17 +703,34 @@ declare const Pickles: { verificationKey: string ): Promise; - dummyBase64Proof: () => string; + loadSrsFp(): WasmFpSrs; + loadSrsFq(): WasmFqSrs; + + dummyProof: ( + maxProofsVerified: N, + domainLog2: number + ) => [N, Pickles.Proof]; + /** * @returns (base64 vk, hash) */ dummyVerificationKey: () => [_: 0, data: string, hash: FieldConst]; + encodeVerificationKey: (vk: MlWrapVerificationKey) => string; + decodeVerificationKey: (vk: string) => MlWrapVerificationKey; + proofToBase64: (proof: [0 | 1 | 2, Pickles.Proof]) => string; - proofOfBase64: ( + proofOfBase64: ( base64: string, - maxProofsVerified: 0 | 1 | 2 - ) => [0 | 1 | 2, Pickles.Proof]; + maxProofsVerified: N + ) => [N, Pickles.Proof]; proofToBase64Transaction: (proof: Pickles.Proof) => string; + + util: { + toMlString(s: string): MlString; + fromMlString(s: MlString): string; + }; }; + +declare function withThreadPool(run: () => Promise): Promise; diff --git a/src/snarky.js b/src/snarky.js index b5b97af00f..8d86b68e0a 100644 --- a/src/snarky.js +++ b/src/snarky.js @@ -1,13 +1,17 @@ -import { getSnarky, withThreadPool } from './bindings/js/wrapper.js'; -import snarkySpec from './bindings/js/snarky-class-spec.js'; -import { proxyClasses } from './bindings/js/proxy.js'; - -export { Snarky, Ledger, Pickles, Test, withThreadPool }; -let isReadyBoolean = true; -let isItReady = () => isReadyBoolean; - -let { Snarky, Ledger, Pickles, Test } = proxyClasses( - getSnarky, - isItReady, - snarkySpec -); +import './bindings/crypto/bindings.js'; +import { wasm, withThreadPool } from './bindings/js/node/node-backend.js'; + +let snarky; + +// this dynamic import makes jest respect the import order +// otherwise the cjs file gets imported before its implicit esm dependencies and fails +CJS: if (typeof require !== 'undefined') { + snarky = require('./bindings/compiled/_node_bindings/o1js_node.bc.cjs'); +} +ESM: snarky = ( + await import('./bindings/compiled/_node_bindings/o1js_node.bc.cjs') +).default; + +let { Snarky, Ledger, Pickles, Test } = snarky; + +export { Snarky, Ledger, Pickles, Test, withThreadPool, wasm }; diff --git a/src/snarky.web.js b/src/snarky.web.js new file mode 100644 index 0000000000..e34800fc63 --- /dev/null +++ b/src/snarky.web.js @@ -0,0 +1,11 @@ +import './bindings/crypto/bindings.js'; +import { initO1, withThreadPool } from './bindings/js/web/web-backend.js'; + +await initO1(); + +let snarky = globalThis.__snarky; +let wasm = globalThis.plonk_wasm; + +let { Snarky, Ledger, Pickles, Test } = snarky; + +export { Snarky, Ledger, Pickles, Test, withThreadPool, wasm }; diff --git a/src/tests/fake-proof.ts b/src/tests/fake-proof.ts new file mode 100644 index 0000000000..430cff43be --- /dev/null +++ b/src/tests/fake-proof.ts @@ -0,0 +1,96 @@ +import { + Mina, + PrivateKey, + SmartContract, + UInt64, + method, + ZkProgram, + verify, +} from 'o1js'; +import assert from 'assert'; + +const RealProgram = ZkProgram({ + name: 'real', + methods: { + make: { + privateInputs: [UInt64], + async method(value: UInt64) { + let expected = UInt64.from(34); + value.assertEquals(expected); + }, + }, + }, +}); + +const FakeProgram = ZkProgram({ + name: 'fake', + methods: { + make: { privateInputs: [UInt64], async method(_: UInt64) {} }, + }, +}); + +class RealProof extends ZkProgram.Proof(RealProgram) {} + +const RecursiveProgram = ZkProgram({ + name: 'broken', + methods: { + verifyReal: { + privateInputs: [RealProof], + async method(proof: RealProof) { + proof.verify(); + }, + }, + }, +}); + +class RecursiveContract extends SmartContract { + @method async verifyReal(proof: RealProof) { + proof.verify(); + } +} + +Mina.setActiveInstance(Mina.LocalBlockchain()); +let publicKey = PrivateKey.random().toPublicKey(); +let zkApp = new RecursiveContract(publicKey); + +await RealProgram.compile(); +await FakeProgram.compile(); +let { verificationKey: contractVk } = await RecursiveContract.compile(); +let { verificationKey: programVk } = await RecursiveProgram.compile(); + +// proof that should be rejected +const fakeProof = await FakeProgram.make(UInt64.from(99999)); +const dummyProof = await RealProof.dummy(undefined, undefined, 0); + +for (let proof of [fakeProof, dummyProof]) { + // zkprogram rejects proof + await assert.rejects(async () => { + await RecursiveProgram.verifyReal(proof); + }, 'recursive program rejects fake proof'); + + // contract rejects proof + await assert.rejects(async () => { + let tx = await Mina.transaction(() => zkApp.verifyReal(proof)); + await tx.prove(); + }, 'recursive contract rejects fake proof'); +} + +// proof that should be accepted +const realProof = await RealProgram.make(UInt64.from(34)); + +// zkprogram accepts proof +const brokenProof = await RecursiveProgram.verifyReal(realProof); +assert( + await verify(brokenProof, programVk.data), + 'recursive program accepts real proof' +); + +// contract accepts proof +let tx = await Mina.transaction(() => zkApp.verifyReal(realProof)); +let [contractProof] = await tx.prove(); +assert( + await verify(contractProof!, contractVk.data), + 'recursive contract accepts real proof' +); + +console.log('fake proof test passed 🎉'); diff --git a/src/tests/inductive-proofs-small.ts b/src/tests/inductive-proofs-small.ts index 271a2bb6cc..acde6a86f6 100644 --- a/src/tests/inductive-proofs-small.ts +++ b/src/tests/inductive-proofs-small.ts @@ -1,16 +1,8 @@ -import { - SelfProof, - Field, - Experimental, - isReady, - shutdown, - Proof, -} from '../index.js'; -import { tic, toc } from '../examples/zkapps/tictoc.js'; +import { SelfProof, Field, ZkProgram, Proof } from 'o1js'; +import { tic, toc } from '../examples/utils/tic-toc.node.js'; -await isReady; - -let MaxProofsVerifiedOne = Experimental.ZkProgram({ +let MaxProofsVerifiedOne = ZkProgram({ + name: 'recursive-1', publicInput: Field, methods: { @@ -45,7 +37,7 @@ async function testRecursion( ) { console.log(`testing maxProofsVerified = ${maxProofsVerified}`); - let ProofClass = Experimental.ZkProgram.Proof(Program); + let ProofClass = ZkProgram.Proof(Program); tic('executing base case'); let initialProof = await Program.baseCase(Field(0)); @@ -84,5 +76,3 @@ function testJsonRoundtrip(ProofClass: any, proof: Proof) { ); return ProofClass.fromJSON(jsonProof); } - -shutdown(); diff --git a/src/tests/inductive-proofs.ts b/src/tests/inductive-proofs.ts index 61c1c9dc1d..101487dae5 100644 --- a/src/tests/inductive-proofs.ts +++ b/src/tests/inductive-proofs.ts @@ -1,16 +1,8 @@ -import { - SelfProof, - Field, - Experimental, - isReady, - shutdown, - Proof, -} from '../index.js'; -import { tic, toc } from '../examples/zkapps/tictoc.js'; - -await isReady; - -let MaxProofsVerifiedZero = Experimental.ZkProgram({ +import { SelfProof, Field, ZkProgram, Proof } from 'o1js'; +import { tic, toc } from '../examples/utils/tic-toc.node.js'; + +let MaxProofsVerifiedZero = ZkProgram({ + name: 'no-recursion', publicInput: Field, methods: { @@ -24,7 +16,8 @@ let MaxProofsVerifiedZero = Experimental.ZkProgram({ }, }); -let MaxProofsVerifiedOne = Experimental.ZkProgram({ +let MaxProofsVerifiedOne = ZkProgram({ + name: 'recursive-1', publicInput: Field, methods: { @@ -47,7 +40,8 @@ let MaxProofsVerifiedOne = Experimental.ZkProgram({ }, }); -let MaxProofsVerifiedTwo = Experimental.ZkProgram({ +let MaxProofsVerifiedTwo = ZkProgram({ + name: 'recursive-2', publicInput: Field, methods: { @@ -100,7 +94,7 @@ async function testRecursion( ) { console.log(`testing maxProofsVerified = ${maxProofsVerified}`); - let ProofClass = Experimental.ZkProgram.Proof(Program); + let ProofClass = ZkProgram.Proof(Program); tic('executing base case'); let initialProof = await Program.baseCase(Field(0)); @@ -152,5 +146,3 @@ function testJsonRoundtrip(ProofClass: any, proof: Proof) { ); return ProofClass.fromJSON(jsonProof); } - -shutdown(); diff --git a/src/tests/transaction-flow.ts b/src/tests/transaction-flow.ts new file mode 100644 index 0000000000..0cba2e73a5 --- /dev/null +++ b/src/tests/transaction-flow.ts @@ -0,0 +1,307 @@ +import { + AccountUpdate, + Provable, + Field, + Lightnet, + Mina, + PrivateKey, + Struct, + PublicKey, + SmartContract, + State, + state, + method, + Reducer, + fetchAccount, + TokenId, +} from 'o1js'; +import assert from 'node:assert'; + +class Event extends Struct({ pub: PublicKey, value: Field }) {} + +class SimpleZkapp extends SmartContract { + @state(Field) x = State(); + @state(Field) counter = State(); + @state(Field) actionState = State(); + + reducer = Reducer({ actionType: Field }); + + events = { + complexEvent: Event, + simpleEvent: Field, + }; + + init() { + super.init(); + this.x.set(Field(2)); + this.counter.set(Field(0)); + this.actionState.set(Reducer.initialActionState); + } + + @method async incrementCounter() { + this.reducer.dispatch(Field(1)); + } + + @method async rollupIncrements() { + const counter = this.counter.get(); + this.counter.requireEquals(counter); + const actionState = this.actionState.get(); + this.actionState.requireEquals(actionState); + + const endActionState = this.account.actionState.getAndRequireEquals(); + + const pendingActions = this.reducer.getActions({ + fromActionState: actionState, + endActionState, + }); + + const { state: newCounter, actionState: newActionState } = + this.reducer.reduce( + pendingActions, + Field, + (state: Field, _action: Field) => { + return state.add(1); + }, + { state: counter, actionState } + ); + + // update on-chain state + this.counter.set(newCounter); + this.actionState.set(newActionState); + } + + @method async update(y: Field, publicKey: PublicKey) { + this.emitEvent('complexEvent', { + pub: publicKey, + value: y, + }); + this.emitEvent('simpleEvent', y); + const x = this.x.getAndRequireEquals(); + this.x.set(x.add(y)); + } +} + +async function testLocalAndRemote( + f: (...args: any[]) => Promise, + ...args: any[] +) { + console.log('⌛ Performing local test'); + Mina.setActiveInstance(Local); + const localResponse = await f(...args); + + console.log('⌛ Performing remote test'); + Mina.setActiveInstance(Remote); + const networkResponse = await f(...args); + + if (localResponse !== undefined && networkResponse !== undefined) { + assert.strictEqual( + JSON.stringify(localResponse), + JSON.stringify(networkResponse) + ); + } + console.log('✅ Test passed'); +} + +async function sendAndVerifyTransaction( + transaction: Mina.Transaction, + throwOnFail = false +) { + await transaction.prove(); + if (throwOnFail) { + const pendingTransaction = await transaction.send(); + return await pendingTransaction.wait(); + } else { + const pendingTransaction = await transaction.safeSend(); + if (pendingTransaction.status === 'pending') { + return await pendingTransaction.safeWait(); + } else { + return pendingTransaction; + } + } +} + +const transactionFee = 100_000_000; + +const Local = Mina.LocalBlockchain(); +const Remote = Mina.Network({ + mina: 'http://localhost:8080/graphql', + archive: 'http://localhost:8282 ', + lightnetAccountManager: 'http://localhost:8181', +}); + +// First set active instance to remote so we can sync up accounts between remote and local ledgers +Mina.setActiveInstance(Remote); + +const senderKey = (await Lightnet.acquireKeyPair()).privateKey; +const sender = senderKey.toPublicKey(); +const zkAppKey = (await Lightnet.acquireKeyPair()).privateKey; +const zkAppAddress = zkAppKey.toPublicKey(); + +// Same balance as remote ledger +const balance = (1550n * 10n ** 9n).toString(); +Local.addAccount(sender, balance); +Local.addAccount(zkAppAddress, balance); + +console.log('Compiling the smart contract.'); +const { verificationKey } = await SimpleZkapp.compile(); +const zkApp = new SimpleZkapp(zkAppAddress); +console.log(''); + +console.log('Testing network auxiliary functions do not throw'); +await testLocalAndRemote(async () => { + await assert.doesNotReject(async () => { + await Mina.transaction({ sender, fee: transactionFee }, async () => { + Mina.getNetworkConstants(); + Mina.getNetworkState(); + Mina.getNetworkId(); + Mina.getProofsEnabled(); + }); + }); +}); +console.log(''); + +console.log( + `Test 'fetchAccount', 'getAccount', and 'hasAccount' match behavior using publicKey: ${zkAppAddress.toBase58()}` +); +await testLocalAndRemote(async () => { + await assert.doesNotReject(async () => { + await fetchAccount({ publicKey: zkAppAddress }); // Must call fetchAccount to populate internal account cache + const account = Mina.getAccount(zkAppAddress); + return { + publicKey: account.publicKey, + nonce: account.nonce, + hasAccount: Mina.hasAccount(zkAppAddress), + }; + }); +}); +console.log(''); + +console.log('Test deploying zkApp for public key ' + zkAppAddress.toBase58()); +await testLocalAndRemote(async () => { + await assert.doesNotReject(async () => { + const transaction = await Mina.transaction( + { sender, fee: transactionFee }, + () => zkApp.deploy({ verificationKey }) + ); + transaction.sign([senderKey, zkAppKey]); + await sendAndVerifyTransaction(transaction); + }); +}); +console.log(''); + +console.log( + "Test calling successful 'update' method does not throw with throwOnFail is true" +); +await testLocalAndRemote(async () => { + await assert.doesNotReject(async () => { + const transaction = await Mina.transaction( + { sender, fee: transactionFee }, + async () => { + await zkApp.update(Field(1), PrivateKey.random().toPublicKey()); + } + ); + transaction.sign([senderKey, zkAppKey]); + const includedTransaction = await sendAndVerifyTransaction( + transaction, + true + ); + assert(includedTransaction.status === 'included'); + await Mina.fetchEvents(zkAppAddress, TokenId.default); + }); +}); +console.log(''); + +console.log( + "Test calling successful 'update' method does not throw with throwOnFail is false" +); +await testLocalAndRemote(async () => { + await assert.doesNotReject(async () => { + const transaction = await Mina.transaction( + { sender, fee: transactionFee }, + async () => { + await zkApp.update(Field(1), PrivateKey.random().toPublicKey()); + } + ); + transaction.sign([senderKey, zkAppKey]); + const includedTransaction = await sendAndVerifyTransaction(transaction); + assert(includedTransaction.status === 'included'); + await Mina.fetchEvents(zkAppAddress, TokenId.default); + }); +}); +console.log(''); + +console.log( + "Test calling failing 'update' expecting 'invalid_fee_access' does not throw with throwOnFail is false" +); +await testLocalAndRemote(async () => { + const transaction = await Mina.transaction( + { sender, fee: transactionFee }, + async () => { + AccountUpdate.fundNewAccount(zkAppAddress); + await zkApp.update(Field(1), PrivateKey.random().toPublicKey()); + } + ); + transaction.sign([senderKey, zkAppKey]); + const rejectedTransaction = await sendAndVerifyTransaction(transaction); + assert(rejectedTransaction.status === 'rejected'); +}); +console.log(''); + +console.log( + "Test calling failing 'update' expecting 'invalid_fee_access' does throw with throwOnFail is true" +); +await testLocalAndRemote(async () => { + await assert.rejects(async () => { + const transaction = await Mina.transaction( + { sender, fee: transactionFee }, + async () => { + AccountUpdate.fundNewAccount(zkAppAddress); + await zkApp.update(Field(1), PrivateKey.random().toPublicKey()); + } + ); + transaction.sign([senderKey, zkAppKey]); + await sendAndVerifyTransaction(transaction, true); + }); +}); +console.log(''); + +console.log('Test emitting and fetching actions do not throw'); +await testLocalAndRemote(async () => { + try { + let transaction = await Mina.transaction( + { sender, fee: transactionFee }, + () => zkApp.incrementCounter() + ); + transaction.sign([senderKey, zkAppKey]); + await sendAndVerifyTransaction(transaction); + + transaction = await Mina.transaction( + { sender, fee: transactionFee }, + async () => zkApp.rollupIncrements() + ); + transaction.sign([senderKey, zkAppKey]); + await sendAndVerifyTransaction(transaction); + + transaction = await Mina.transaction( + { sender, fee: transactionFee }, + async () => { + await zkApp.incrementCounter(); + await zkApp.incrementCounter(); + await zkApp.incrementCounter(); + await zkApp.incrementCounter(); + await zkApp.incrementCounter(); + } + ); + transaction.sign([senderKey, zkAppKey]); + await sendAndVerifyTransaction(transaction); + + transaction = await Mina.transaction( + { sender, fee: transactionFee }, + async () => zkApp.rollupIncrements() + ); + transaction.sign([senderKey, zkAppKey]); + await sendAndVerifyTransaction(transaction); + } catch (error) { + assert.ifError(error); + } +}); diff --git a/tests/artifacts/javascript/e2eTestsHelpers.js b/tests/artifacts/javascript/e2e-tests-helpers.js similarity index 100% rename from tests/artifacts/javascript/e2eTestsHelpers.js rename to tests/artifacts/javascript/e2e-tests-helpers.js diff --git a/tests/artifacts/javascript/on-chain-state-mgmt-zkapp-ui.js b/tests/artifacts/javascript/on-chain-state-mgmt-zkapp-ui.js index 1c8739b9dd..e6942519a5 100644 --- a/tests/artifacts/javascript/on-chain-state-mgmt-zkapp-ui.js +++ b/tests/artifacts/javascript/on-chain-state-mgmt-zkapp-ui.js @@ -1,18 +1,9 @@ -import { logEvents } from './e2eTestsHelpers.js'; +import { logEvents } from './e2e-tests-helpers.js'; import { adminPrivateKey, HelloWorld, -} from './examples/zkapps/hello_world/hello_world.js'; -import { - AccountUpdate, - Field, - isReady, - Mina, - PrivateKey, - verify, -} from './index.js'; - -await isReady; +} from './examples/zkapps/hello-world/hello-world.js'; +import { AccountUpdate, Field, Mina, PrivateKey, verify } from './index.js'; const deployButton = document.querySelector('#deployButton'); const updateButton = document.querySelector('#updateButton'); @@ -21,7 +12,7 @@ const eventsContainer = document.querySelector('#eventsContainer'); const zkAppStateContainer = document.querySelector('#zkAppStateContainer'); logEvents( - `SnarkyJS initialized after ${performance.now().toFixed(2)}ms`, + `o1js initialized after ${performance.now().toFixed(2)}ms`, eventsContainer ); @@ -44,11 +35,11 @@ deployButton.addEventListener('click', async () => { try { await HelloWorld.compile(); - const deploymentTransaction = await Mina.transaction(feePayer, () => { + const deploymentTransaction = await Mina.transaction(feePayer, async () => { if (!eventsContainer.innerHTML.includes('zkApp Deployed successfully')) { AccountUpdate.fundNewAccount(feePayer); } - zkAppInstance.deploy(); + await zkAppInstance.deploy(); }); await deploymentTransaction.sign([feePayerKey, zkAppPrivateKey]).send(); @@ -84,8 +75,8 @@ updateButton.addEventListener('click', async (event) => { `Updating zkApp State from ${currentState} to ${zkAppStateValue.value} with Admin Private Key and using form data: ${formData}...`, eventsContainer ); - const transaction = await Mina.transaction(feePayer, () => { - zkAppInstance.update( + const transaction = await Mina.transaction(feePayer, async () => { + await zkAppInstance.update( Field(parseInt(zkAppStateValue.value)), adminPrivateKey ); diff --git a/tests/integration/inductive-proofs.js b/tests/integration/inductive-proofs.js deleted file mode 100644 index 5fe77f4ced..0000000000 --- a/tests/integration/inductive-proofs.js +++ /dev/null @@ -1,148 +0,0 @@ -import { - SelfProof, - Field, - Experimental, - isReady, - shutdown, -} from '../../dist/node/index.js'; -import { tic, toc } from './tictoc.js'; - -await isReady; - -let MaxProofsVerifiedZero = Experimental.ZkProgram({ - publicInput: Field, - - methods: { - baseCase: { - privateInputs: [], - - method(publicInput) { - publicInput.assertEquals(Field(0)); - }, - }, - }, -}); - -let MaxProofsVerifiedOne = Experimental.ZkProgram({ - publicInput: Field, - - methods: { - baseCase: { - privateInputs: [], - - method(publicInput) { - publicInput.assertEquals(Field(0)); - }, - }, - - mergeOne: { - privateInputs: [SelfProof], - - method(publicInput, earlierProof) { - earlierProof.verify(); - earlierProof.publicInput.add(1).assertEquals(publicInput); - }, - }, - }, -}); - -let MaxProofsVerifiedTwo = Experimental.ZkProgram({ - publicInput: Field, - - methods: { - baseCase: { - privateInputs: [], - - method(publicInput) { - publicInput.assertEquals(Field(0)); - }, - }, - - mergeOne: { - privateInputs: [SelfProof], - - method(publicInput, earlierProof) { - earlierProof.verify(); - earlierProof.publicInput.add(1).assertEquals(publicInput); - }, - }, - - mergeTwo: { - privateInputs: [SelfProof, SelfProof], - - method(publicInput, p1, p2) { - p1.verify(); - p1.publicInput.add(1).assertEquals(p2.publicInput); - p2.verify(); - p2.publicInput.add(1).assertEquals(publicInput); - }, - }, - }, -}); -tic('compiling three programs..'); -await MaxProofsVerifiedZero.compile(); -await MaxProofsVerifiedOne.compile(); -await MaxProofsVerifiedTwo.compile(); -toc(); - -await testRecursion(MaxProofsVerifiedZero, 0); -await testRecursion(MaxProofsVerifiedOne, 1); -await testRecursion(MaxProofsVerifiedTwo, 2); - -async function testRecursion(Program, maxProofsVerified) { - console.log(`testing maxProofsVerified = ${maxProofsVerified}`); - - let ProofClass = Experimental.ZkProgram.Proof(Program); - - tic('executing base case..'); - let initialProof = await Program.baseCase(Field(0)); - toc(); - initialProof = testJsonRoundtrip(ProofClass, initialProof); - initialProof.verify(); - initialProof.publicInput.assertEquals(Field(0)); - - if (initialProof.maxProofsVerified != maxProofsVerified) { - throw Error( - `Expected initialProof to have maxProofsVerified = ${maxProofsVerified} but has ${initialProof.maxProofsVerified}` - ); - } - - let p1, p2; - if (initialProof.maxProofsVerified == 0) return; - - tic('executing mergeOne..'); - p1 = await Program.mergeOne(Field(1), initialProof); - toc(); - p1 = testJsonRoundtrip(ProofClass, p1); - p1.verify(); - p1.publicInput.assertEquals(Field(1)); - if (p1.maxProofsVerified != maxProofsVerified) { - throw Error( - `Expected p1 to have maxProofsVerified = ${maxProofsVerified} but has ${p1.maxProofsVerified}` - ); - } - - if (initialProof.maxProofsVerified == 1) return; - tic('executing mergeTwo..'); - p2 = await Program.mergeTwo(Field(2), initialProof, p1); - toc(); - p2 = testJsonRoundtrip(ProofClass, p2); - p2.verify(); - p2.publicInput.assertEquals(Field(2)); - if (p2.maxProofsVerified != maxProofsVerified) { - throw Error( - `Expected p2 to have maxProofsVerified = ${maxProofsVerified} but has ${p2.maxProofsVerified}` - ); - } -} - -function testJsonRoundtrip(ProofClass, proof) { - let jsonProof = proof.toJSON(); - console.log( - 'json roundtrip', - JSON.stringify({ ...jsonProof, proof: jsonProof.proof.slice(0, 10) + '..' }) - ); - return ProofClass.fromJSON(jsonProof); -} - -shutdown(); diff --git a/tests/integration/package-lock.json b/tests/integration/package-lock.json deleted file mode 100644 index 2a149f41d5..0000000000 --- a/tests/integration/package-lock.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "name": "snarkyjs-integration-tests", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "snarkyjs-integration-tests", - "version": "0.0.0" - }, - "../..": { - "version": "0.9.5", - "extraneous": true, - "license": "Apache-2.0", - "dependencies": { - "blakejs": "1.2.1", - "detect-gpu": "^5.0.5", - "env": "^0.0.2", - "isomorphic-fetch": "^3.0.0", - "js-sha256": "^0.9.0", - "reflect-metadata": "^0.1.13", - "tslib": "^2.3.0" - }, - "bin": { - "snarky-run": "src/build/run.js" - }, - "devDependencies": { - "@playwright/test": "^1.25.2", - "@types/isomorphic-fetch": "^0.0.36", - "@types/jest": "^27.0.0", - "@types/node": "^18.14.2", - "@typescript-eslint/eslint-plugin": "^5.0.0", - "esbuild": "^0.16.16", - "eslint": "^8.0.0", - "expect": "^29.0.1", - "fs-extra": "^10.0.0", - "glob": "^8.0.3", - "howslow": "^0.1.0", - "jest": "^28.1.3", - "minimist": "^1.2.7", - "prettier": "^2.8.4", - "replace-in-file": "^6.3.5", - "rimraf": "^3.0.2", - "ts-jest": "^28.0.8", - "typedoc": "^0.23.26", - "typescript": "^4.9.5" - }, - "engines": { - "node": ">=16.4.0" - } - } - } -} diff --git a/tests/integration/package.json b/tests/integration/package.json deleted file mode 100644 index 0a20851359..0000000000 --- a/tests/integration/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "snarkyjs-integration-tests", - "version": "0.0.0", - "type": "module", - "dependencies": {} -} diff --git a/tests/integration/simple-zkapp-mock-apply.js b/tests/integration/simple-zkapp-mock-apply.js deleted file mode 100644 index a5edb82849..0000000000 --- a/tests/integration/simple-zkapp-mock-apply.js +++ /dev/null @@ -1,132 +0,0 @@ -import { - Field, - declareState, - declareMethods, - State, - PrivateKey, - SmartContract, - isReady, - shutdown, - Mina, - Permissions, - verify, - AccountUpdate, -} from '../../dist/node/index.js'; -import { tic, toc } from './tictoc.js'; - -await isReady; - -// declare the zkapp -const initialState = Field(1); -class SimpleZkapp extends SmartContract { - constructor(address) { - super(address); - this.x = State(); - } - - deploy(args) { - super.deploy(args); - this.account.permissions.set({ - ...Permissions.default(), - editState: Permissions.proofOrSignature(), - }); - } - - init() { - super.init(); - this.x.set(initialState); - } - - update(y) { - let x = this.x.get(); - this.x.assertEquals(x); - y.assertGreaterThan(0); - this.x.set(x.add(y)); - } -} -// note: this is our non-typescript way of doing what our decorators do -declareState(SimpleZkapp, { x: Field }); -declareMethods(SimpleZkapp, { init: [], update: [Field] }); - -// setup mock mina -let Local = Mina.LocalBlockchain(); -Mina.setActiveInstance(Local); -let { publicKey: sender, privateKey: senderKey } = Local.testAccounts[0]; - -let zkappKey = PrivateKey.random(); -let zkappAddress = zkappKey.toPublicKey(); -let zkapp = new SimpleZkapp(zkappAddress); - -tic('compute circuit digest'); -SimpleZkapp.digest(); -toc(); - -tic('compile smart contract'); -let { verificationKey } = await SimpleZkapp.compile(); -toc(); - -tic('create deploy transaction (with proof)'); -let deployTx = await Mina.transaction(sender, () => { - AccountUpdate.fundNewAccount(sender); - zkapp.deploy(); -}); -let [, , proof] = await deployTx.prove(); -deployTx.sign([zkappKey, senderKey]); -toc(); - -tic('verify transaction proof'); -let ok = await verify(proof, verificationKey.data); -toc(); -console.log('did proof verify?', ok); -if (!ok) throw Error("proof didn't verify"); - -tic('apply deploy transaction'); -await deployTx.send(); -toc(); - -// check that deploy and initialize txns were applied -let zkappState = zkapp.x.get(); -zkappState.assertEquals(1); -console.log('got initial state: ' + zkappState); - -tic('create update transaction (no proof)'); -let tx = await Mina.transaction(sender, () => { - zkapp.update(Field(2)); - zkapp.requireSignature(); -}); -tx.sign([senderKey, zkappKey]); -toc(); - -tic('apply update transaction (no proof)'); -await tx.send(); -toc(); - -// check that first update txn was applied -zkappState = zkapp.x.get(); -zkappState.assertEquals(3); -console.log('got updated state: ' + zkappState); - -tic('create update transaction (with proof)'); -tx = await Mina.transaction(sender, () => { - zkapp.update(Field(2)); -}); -[proof] = await tx.prove(); -tx.sign([senderKey]); -toc(); - -tic('verify transaction proof'); -ok = await verify(proof, verificationKey.data); -toc(); -console.log('did proof verify?', ok); -if (!ok) throw Error("proof didn't verify"); - -tic('apply update transaction (with proof)'); -await tx.send(); -toc(); - -// check that second update txn was applied -zkappState = zkapp.x.get(); -zkappState.assertEquals(5); -console.log('got updated state: ' + zkappState); - -shutdown(); diff --git a/tests/integration/simple-zkapp.js b/tests/integration/simple-zkapp.js deleted file mode 100644 index a7a64d46c2..0000000000 --- a/tests/integration/simple-zkapp.js +++ /dev/null @@ -1,259 +0,0 @@ -import { - Field, - declareState, - declareMethods, - State, - PrivateKey, - SmartContract, - isReady, - Mina, - PublicKey, - UInt64, - AccountUpdate, - Bool, - shutdown, - Permissions, - fetchAccount, -} from 'snarkyjs'; - -await isReady; - -class NotSoSimpleZkapp extends SmartContract { - events = { update: Field, payout: UInt64, payoutReceiver: PublicKey }; - - constructor(address) { - super(address); - this.x = State(); - } - - init() { - super.init(); - this.x.set(initialState); - this.account.permissions.set({ - ...Permissions.default(), - send: Permissions.proof(), - editState: Permissions.proof(), - }); - } - - update(y) { - let x = this.x.get(); - this.x.assertEquals(x); - y.assertGreaterThan(0); - this.x.set(x.add(y)); - } - - payout(caller) { - let callerAddress = caller.toPublicKey(); - callerAddress.assertEquals(privilegedAddress); - - let callerAccountUpdate = AccountUpdate.create(callerAddress); - callerAccountUpdate.account.isNew.assertEquals(Bool(true)); - - let balance = this.account.balance.get(); - this.account.balance.assertEquals(balance); - let halfBalance = balance.div(2); - this.send({ to: callerAccountUpdate, amount: halfBalance }); - - // emit some events - this.emitEvent('payoutReceiver', callerAddress); - this.emitEvent('payout', halfBalance); - } - - deposit(amount) { - let senderUpdate = AccountUpdate.createSigned(this.sender); - senderUpdate.send({ to: this, amount }); - } -} -// note: this is our non-typescript way of doing what our decorators do -declareState(NotSoSimpleZkapp, { x: Field }); -declareMethods(NotSoSimpleZkapp, { - update: [Field], - payout: [PrivateKey], - deposit: [UInt64], -}); - -// slightly adjusted polling parameters for tx.wait() -const waitParams = { - maxAttempts: 30, - interval: 45000, -}; - -// parse command line; for local testing, use random keys as fallback -let [feePayerKeyBase58, graphql_uri] = process.argv.slice(2); - -let isLocal = false; - -if (feePayerKeyBase58 === 'local') { - isLocal = true; - let LocalNetwork = Mina.LocalBlockchain(graphql_uri); - Mina.setActiveInstance(LocalNetwork); - let { privateKey } = LocalNetwork.testAccounts[0]; - feePayerKeyBase58 = privateKey.toBase58(); -} else { - if (!graphql_uri) throw Error('Graphql uri is undefined, aborting'); - if (!feePayerKeyBase58) throw Error('Fee payer key is undefined, aborting'); - let LocalNetwork = Mina.Network(graphql_uri); - Mina.setActiveInstance(LocalNetwork); -} - -let zkappKey = PrivateKey.random(); -let zkappAddress = zkappKey.toPublicKey(); - -let feePayerKey = PrivateKey.fromBase58(feePayerKeyBase58); -let feePayerAddress = feePayerKey.toPublicKey(); - -if (!isLocal) { - let res = await fetchAccount({ - publicKey: feePayerAddress, - }); - if (res.error) { - throw Error( - `The fee payer account needs to be funded in order for the script to succeed! Please provide the private key of an already funded account. ${feePayerAddress.toBase58()}, ${feePayerKeyBase58}\n\n${ - res.error.message - }` - ); - } -} - -// a special account that is allowed to pull out half of the zkapp balance, once -let privilegedKey = PrivateKey.random(); -let privilegedAddress = privilegedKey.toPublicKey(); - -let zkappTargetBalance = 10_000_000_000; -let initialBalance = zkappTargetBalance; -let initialState = Field(1); - -console.log( - `simple-zkapp.js: Running with zkapp address ${zkappKey - .toPublicKey() - .toBase58()}, fee payer address ${feePayerAddress.toBase58()} and graphql uri ${graphql_uri}\n\n` -); - -console.log(`simple-zkapp.js: Starting integration test\n`); - -let zkapp = new NotSoSimpleZkapp(zkappAddress); -await NotSoSimpleZkapp.compile(); - -console.log('deploying contract\n'); -let tx = await Mina.transaction( - { sender: feePayerAddress, fee: 100_000_000 }, - () => { - AccountUpdate.fundNewAccount(feePayerAddress); - - zkapp.deploy(); - } -); -await tx.prove(); -await (await tx.sign([feePayerKey, zkappKey]).send()).wait(waitParams); - -if (!isLocal) await fetchAccount({ publicKey: zkappAddress }); -let zkappAccount = Mina.getAccount(zkappAddress); - -// we deployed the contract with an initial state of 1 -expectAssertEquals(zkappAccount.zkapp.appState[0], Field(1)); - -// the fresh zkapp account shouldn't have any funds -expectAssertEquals(zkappAccount.balance, UInt64.from(0)); - -console.log('deposit funds\n'); -tx = await Mina.transaction( - { sender: feePayerAddress, fee: 100_000_000 }, - () => { - zkapp.deposit(UInt64.from(initialBalance)); - } -); -await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(waitParams); - -if (!isLocal) await fetchAccount({ publicKey: zkappAddress }); -zkappAccount = Mina.getAccount(zkappAddress); - -// we deposit 10_000_000_000 funds into the zkapp account -expectAssertEquals(zkappAccount.balance, UInt64.from(initialBalance)); - -console.log('update 1\n'); -tx = await Mina.transaction( - { sender: feePayerAddress, fee: 100_000_000 }, - () => { - zkapp.update(Field(30)); - } -); -await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(waitParams); - -console.log('update 2\n'); -tx = await Mina.transaction( - { sender: feePayerAddress, fee: 100_000_000 }, - () => { - zkapp.update(Field(100)); - } -); -await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(waitParams); - -if (!isLocal) await fetchAccount({ publicKey: zkappAddress }); -zkappAccount = Mina.getAccount(zkappAddress); - -// no balance change expected -expectAssertEquals(zkappAccount.balance, UInt64.from(initialBalance)); - -// we updated the zkapp state to 131 -expectAssertEquals(zkappAccount.zkapp.appState[0], Field(131)); - -console.log('payout 1\n'); -tx = await Mina.transaction( - { sender: feePayerAddress, fee: 100_000_000 }, - () => { - AccountUpdate.fundNewAccount(feePayerAddress); - zkapp.payout(privilegedKey); - } -); -await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(waitParams); - -if (!isLocal) await fetchAccount({ publicKey: zkappAddress }); -zkappAccount = Mina.getAccount(zkappAddress); - -// we withdraw (payout) half of the initial balance -expectAssertEquals(zkappAccount.balance, UInt64.from(initialBalance / 2)); - -console.log('payout 2 (expected to fail)\n'); -tx = await Mina.transaction( - { sender: feePayerAddress, fee: 100_000_000 }, - () => { - zkapp.payout(privilegedKey); - } -); - -await tx.prove(); - -// this tx should fail -try { - let txId = await tx.sign([feePayerKey]).send(); - await txId.wait(waitParams); -} catch (err) { - // throw if this is not the expected error - if (!err.message.includes('Account_is_new_precondition_unsatisfied')) { - throw err; - } -} - -// although we just checked above that the tx failed, I just would like to double-check that anyway (cross checking logic) -if (!isLocal) await fetchAccount({ publicKey: zkappAddress }); -zkappAccount = Mina.getAccount(zkappAddress); - -// checking that state hasn't changed - we expect the tx to fail so the state should equal previous state -expectAssertEquals(zkappAccount.balance, UInt64.from(initialBalance / 2)); - -function expectAssertEquals(actual, expected) { - try { - actual.assertEquals(expected); - } catch (error) { - throw Error( - `Expected value ${expected.toString()}, but got ${actual.toString()}` - ); - } -} - -shutdown(); diff --git a/tests/integration/tictoc.js b/tests/integration/tictoc.js deleted file mode 100644 index cee8f0e64b..0000000000 --- a/tests/integration/tictoc.js +++ /dev/null @@ -1,17 +0,0 @@ -// helper for printing timings - -export { tic, toc }; - -let timingStack = []; -let i = 0; - -function tic(label = `Run command ${i++}`) { - process.stdout.write(`${label}... `); - timingStack.push([label, Date.now()]); -} - -function toc() { - let [label, start] = timingStack.pop(); - let time = (Date.now() - start) / 1000; - process.stdout.write(`\r${label}... ${time.toFixed(3)} sec\n`); -} diff --git a/tests/on-chain-state-mgmt-zkapp-ui.spec.ts b/tests/on-chain-state-mgmt-zkapp-ui.spec.ts index 7ca6a00c01..7fba7fd26d 100644 --- a/tests/on-chain-state-mgmt-zkapp-ui.spec.ts +++ b/tests/on-chain-state-mgmt-zkapp-ui.spec.ts @@ -1,11 +1,11 @@ import { test } from './fixtures/on-chain-state-mgmt-zkapp.js'; test.describe('On-Chain State Management zkApp UI', () => { - test('should load page and initialize SnarkyJS', async ({ + test('should load page and initialize o1js', async ({ onChainStateMgmtZkAppPage, }) => { await onChainStateMgmtZkAppPage.goto(); - await onChainStateMgmtZkAppPage.checkSnarkyJsInitialization(); + await onChainStateMgmtZkAppPage.checkO1jsInitialization(); }); test('should fail to update account state since zkApp was not yet deployed', async ({ @@ -14,7 +14,7 @@ test.describe('On-Chain State Management zkApp UI', () => { test.skip(process.env.CI === 'true', 'Skipping test in CI'); await onChainStateMgmtZkAppPage.goto(); - await onChainStateMgmtZkAppPage.checkSnarkyJsInitialization(); + await onChainStateMgmtZkAppPage.checkO1jsInitialization(); await onChainStateMgmtZkAppPage.updateZkAppState('3'); await onChainStateMgmtZkAppPage.checkZkAppStateUpdateFailureByUnknownAccount(); }); @@ -23,7 +23,7 @@ test.describe('On-Chain State Management zkApp UI', () => { onChainStateMgmtZkAppPage, }) => { await onChainStateMgmtZkAppPage.goto(); - await onChainStateMgmtZkAppPage.checkSnarkyJsInitialization(); + await onChainStateMgmtZkAppPage.checkO1jsInitialization(); await onChainStateMgmtZkAppPage.compileAndDeployZkApp(); await onChainStateMgmtZkAppPage.checkDeployedZkApp(); }); @@ -35,7 +35,7 @@ test.describe('On-Chain State Management zkApp UI', () => { const newAccountState = '4'; await onChainStateMgmtZkAppPage.goto(); - await onChainStateMgmtZkAppPage.checkSnarkyJsInitialization(); + await onChainStateMgmtZkAppPage.checkO1jsInitialization(); await onChainStateMgmtZkAppPage.compileAndDeployZkApp(); await onChainStateMgmtZkAppPage.checkDeployedZkApp(); await onChainStateMgmtZkAppPage.updateZkAppState(newAccountState); @@ -52,7 +52,7 @@ test.describe('On-Chain State Management zkApp UI', () => { const newAccountState = '4'; await onChainStateMgmtZkAppPage.goto(); - await onChainStateMgmtZkAppPage.checkSnarkyJsInitialization(); + await onChainStateMgmtZkAppPage.checkO1jsInitialization(); await onChainStateMgmtZkAppPage.compileAndDeployZkApp(); await onChainStateMgmtZkAppPage.checkDeployedZkApp(); await onChainStateMgmtZkAppPage.updateZkAppState(newAccountState); @@ -70,7 +70,7 @@ test.describe('On-Chain State Management zkApp UI', () => { test.skip(process.env.CI === 'true', 'Skipping test in CI'); await onChainStateMgmtZkAppPage.goto(); - await onChainStateMgmtZkAppPage.checkSnarkyJsInitialization(); + await onChainStateMgmtZkAppPage.checkO1jsInitialization(); await onChainStateMgmtZkAppPage.compileAndDeployZkApp(); await onChainStateMgmtZkAppPage.checkDeployedZkApp(); await onChainStateMgmtZkAppPage.clearEvents(); @@ -88,7 +88,7 @@ test.describe('On-Chain State Management zkApp UI', () => { const nextAccountState = '16'; await onChainStateMgmtZkAppPage.goto(); - await onChainStateMgmtZkAppPage.checkSnarkyJsInitialization(); + await onChainStateMgmtZkAppPage.checkO1jsInitialization(); await onChainStateMgmtZkAppPage.compileAndDeployZkApp(); await onChainStateMgmtZkAppPage.checkDeployedZkApp(); await onChainStateMgmtZkAppPage.updateZkAppState(newAccountState); diff --git a/tests/pages/on-chain-state-mgmt-zkapp.ts b/tests/pages/on-chain-state-mgmt-zkapp.ts index fae58b9e73..092ae65bf4 100644 --- a/tests/pages/on-chain-state-mgmt-zkapp.ts +++ b/tests/pages/on-chain-state-mgmt-zkapp.ts @@ -36,10 +36,8 @@ export class OnChainStateMgmtZkAppPage { await this.clearEventsButton.click(); } - async checkSnarkyJsInitialization() { - await expect(this.eventsContainer).toContainText( - 'SnarkyJS initialized after' - ); + async checkO1jsInitialization() { + await expect(this.eventsContainer).toContainText('o1js initialized after'); } async checkDeployedZkApp() { diff --git a/tests/vk-regression/diverse-zk-program-run.ts b/tests/vk-regression/diverse-zk-program-run.ts new file mode 100644 index 0000000000..7aa57007dc --- /dev/null +++ b/tests/vk-regression/diverse-zk-program-run.ts @@ -0,0 +1,11 @@ +import assert from 'node:assert'; +import { diverse, Bytes128 } from './diverse-zk-program.js'; + +console.log('testing proof generation for diverse program'); +await diverse.compile(); + +let proof1 = await diverse.sha3(Bytes128.fromString('hello')); +assert(await diverse.verify(proof1), 'verifies'); + +let proof2 = await diverse.recursive(proof1); +assert(await diverse.verify(proof2), 'verifies'); diff --git a/tests/vk-regression/diverse-zk-program.ts b/tests/vk-regression/diverse-zk-program.ts new file mode 100644 index 0000000000..0261c72b94 --- /dev/null +++ b/tests/vk-regression/diverse-zk-program.ts @@ -0,0 +1,97 @@ +import { + ZkProgram, + Crypto, + createEcdsa, + createForeignCurve, + Bytes, + assert, + Provable, + Field, + Hash, + MerkleWitness, + PublicKey, + PrivateKey, + Signature, + AccountUpdate, + SelfProof, +} from 'o1js'; + +export { diverse, Bytes128 }; + +class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} +class Secp256k1Scalar extends Secp256k1.Scalar {} +class Secp256k1Signature extends createEcdsa(Secp256k1) {} + +class Bytes128 extends Bytes(128) {} + +class MerkleWitness30 extends MerkleWitness(30) {} + +const diverse = ZkProgram({ + name: 'diverse', + + methods: { + // foreign field / curve ops, multi-range checks + ecdsa: { + privateInputs: [ + Secp256k1Scalar.provable, + Secp256k1Signature.provable, + Secp256k1.provable, + ], + async method( + message: Secp256k1Scalar, + signature: Secp256k1Signature, + publicKey: Secp256k1 + ) { + assert(signature.verifySignedHash(message, publicKey)); + }, + }, + + // bitwise gadgets + sha3: { + privateInputs: [Bytes128.provable], + async method(xs: Bytes128) { + Hash.SHA3_256.hash(xs); + }, + }, + + // poseidon + poseidon: { + privateInputs: [AccountUpdate, MerkleWitness30], + async method(accountUpdate: AccountUpdate, witness: MerkleWitness30) { + let leaf = accountUpdate.hash(); + let root = witness.calculateRoot(leaf); + let index = witness.calculateIndex(); + index.assertNotEquals(root); + }, + }, + + // native EC ops + pallas: { + privateInputs: [PublicKey, PrivateKey, Signature], + async method(pk: PublicKey, sk: PrivateKey, sig: Signature) { + let pk2 = sk.toPublicKey(); + pk.assertEquals(pk2); + + sig.verify(pk, [Field(1), Field(2)]).assertTrue(); + }, + }, + + // only generic gates + generic: { + privateInputs: [Field, Field], + async method(x: Field, y: Field) { + x.square().equals(5).assertFalse(); + let z = Provable.if(y.equals(0), x, y); + z.assertEquals(x); + }, + }, + + // recursive proof + recursive: { + privateInputs: [SelfProof], + async method(proof: SelfProof) { + proof.verify(); + }, + }, + }, +}); diff --git a/tests/vk-regression/plain-constraint-system.ts b/tests/vk-regression/plain-constraint-system.ts new file mode 100644 index 0000000000..17414694e6 --- /dev/null +++ b/tests/vk-regression/plain-constraint-system.ts @@ -0,0 +1,210 @@ +import { + Field, + Group, + Gadgets, + Provable, + Scalar, + Hash, + Bytes, + Bool, + UInt64, +} from 'o1js'; + +export { GroupCS, BitwiseCS, HashCS, BasicCS }; + +const GroupCS = constraintSystem('Group Primitive', { + add() { + let g1 = Provable.witness(Group, () => Group.generator); + let g2 = Provable.witness(Group, () => Group.generator); + g1.add(g2); + }, + sub() { + let g1 = Provable.witness(Group, () => Group.generator); + let g2 = Provable.witness(Group, () => Group.generator); + g1.sub(g2); + }, + scale() { + let g1 = Provable.witness(Group, () => Group.generator); + let s = Provable.witness(Scalar, () => Scalar.from(5n)); + g1.scale(s); + }, + equals() { + let g1 = Provable.witness(Group, () => Group.generator); + let g2 = Provable.witness(Group, () => Group.generator); + g1.equals(g2).assertTrue(); + g1.equals(g2).assertFalse(); + g1.equals(g2).assertEquals(true); + g1.equals(g2).assertEquals(false); + }, + assertions() { + let g1 = Provable.witness(Group, () => Group.generator); + let g2 = Provable.witness(Group, () => Group.generator); + g1.assertEquals(g2); + }, +}); + +const BitwiseCS = constraintSystem('Bitwise Primitive', { + rot32() { + let a = Provable.witness(Field, () => new Field(12)); + Gadgets.rotate32(a, 2, 'left'); + Gadgets.rotate32(a, 2, 'right'); + Gadgets.rotate32(a, 4, 'left'); + Gadgets.rotate32(a, 4, 'right'); + }, + rot64() { + let a = Provable.witness(Field, () => new Field(12)); + Gadgets.rangeCheck64(a); // `rotate()` doesn't do this + Gadgets.rotate64(a, 2, 'left'); + Gadgets.rotate64(a, 2, 'right'); + Gadgets.rotate64(a, 4, 'left'); + Gadgets.rotate64(a, 4, 'right'); + }, + xor() { + let a = Provable.witness(Field, () => new Field(5n)); + let b = Provable.witness(Field, () => new Field(5n)); + Gadgets.xor(a, b, 16); + Gadgets.xor(a, b, 32); + Gadgets.xor(a, b, 48); + Gadgets.xor(a, b, 64); + }, + notUnchecked() { + let a = Provable.witness(Field, () => new Field(5n)); + Gadgets.not(a, 16, false); + Gadgets.not(a, 32, false); + Gadgets.not(a, 48, false); + Gadgets.not(a, 64, false); + }, + notChecked() { + let a = Provable.witness(Field, () => new Field(5n)); + Gadgets.not(a, 16, true); + Gadgets.not(a, 32, true); + Gadgets.not(a, 48, true); + Gadgets.not(a, 64, true); + }, + leftShift() { + let a = Provable.witness(Field, () => new Field(12)); + Gadgets.leftShift64(a, 2); + Gadgets.leftShift64(a, 4); + }, + rightShift() { + let a = Provable.witness(Field, () => new Field(12)); + Gadgets.rightShift64(a, 2); + Gadgets.rightShift64(a, 4); + }, + and() { + let a = Provable.witness(Field, () => new Field(5n)); + let b = Provable.witness(Field, () => new Field(5n)); + Gadgets.and(a, b, 16); + Gadgets.and(a, b, 32); + Gadgets.and(a, b, 48); + Gadgets.and(a, b, 64); + }, +}); + +const Bytes32 = Bytes(32); +const bytes32 = Bytes32.from([]); + +const HashCS = constraintSystem('Hashes', { + SHA256() { + let xs = Provable.witness(Bytes32.provable, () => bytes32); + Hash.SHA3_256.hash(xs); + }, + + SHA384() { + let xs = Provable.witness(Bytes32.provable, () => bytes32); + Hash.SHA3_384.hash(xs); + }, + + SHA512() { + let xs = Provable.witness(Bytes32.provable, () => bytes32); + Hash.SHA3_512.hash(xs); + }, + + Keccak256() { + let xs = Provable.witness(Bytes32.provable, () => bytes32); + Hash.Keccak256.hash(xs); + }, + + Poseidon() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(Field, () => Field(x)) + ); + Hash.Poseidon.hash(xs); + }, +}); + +const witness = () => Provable.witness(Field, () => Field(0)); + +const BasicCS = constraintSystem('Basic', { + equals() { + let [x, y, z] = [witness(), witness(), witness()]; + x.equals(y); + z.equals(1); + }, + if() { + let b = Provable.witness(Bool, () => Bool(false)); + let [x, y, z] = [witness(), witness(), witness()]; + Provable.if(b, x, y); + Provable.if(b, z, Field(1)); + }, + toBits() { + let x = witness(); + x.toBits(); + }, + + // comparisons + assertLessThan() { + let [x, y] = [witness(), witness()]; + x.assertLessThan(y); + }, + lessThan() { + let [x, y] = [witness(), witness()]; + x.lessThan(y); + }, + assertLessThanUInt64() { + let x = Provable.witness(UInt64, () => new UInt64(0)); + let y = Provable.witness(UInt64, () => new UInt64(0)); + x.assertLessThan(y); + }, + lessThanUInt64() { + let x = Provable.witness(UInt64, () => new UInt64(0)); + let y = Provable.witness(UInt64, () => new UInt64(0)); + x.lessThan(y); + }, +}); + +// mock ZkProgram API for testing + +function constraintSystem( + name: string, + obj: { [K: string]: (...args: any) => void } +) { + let methodKeys = Object.keys(obj); + + return { + async analyzeMethods() { + let cs: Record< + string, + { + rows: number; + digest: string; + } + > = {}; + for (let key of methodKeys) { + let { rows, digest } = await Provable.constraintSystem(obj[key]); + cs[key] = { + digest, + rows, + }; + } + return cs; + }, + async compile() { + return { + verificationKey: { data: '', hash: '' }, + }; + }, + name, + digest: async () => name, + }; +} diff --git a/tests/vk-regression/tsconfig.json b/tests/vk-regression/tsconfig.json new file mode 100644 index 0000000000..702d13e4a4 --- /dev/null +++ b/tests/vk-regression/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "include": ["."], + "exclude": [], + "compilerOptions": { + "rootDir": "../..", + "baseUrl": "../..", + "paths": { + "o1js": ["."] + } + } +} diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json new file mode 100644 index 0000000000..d97ab5584e --- /dev/null +++ b/tests/vk-regression/vk-regression.json @@ -0,0 +1,323 @@ +{ + "Voting_": { + "digest": "16229b84457193fbfae9e8bc71a5428d4b2d4666dacbd1bed9cebdbdb2128eb7", + "methods": { + "voterRegistration": { + "rows": 1178, + "digest": "0ae50f9f93602b0429b02f6e7d65a75c" + }, + "candidateRegistration": { + "rows": 1178, + "digest": "aac5e35c5d37d8125c337752f0bd2939" + }, + "approveRegistrations": { + "rows": 1146, + "digest": "bb03d782c6dee33a6ad4949886eda241" + }, + "vote": { + "rows": 1508, + "digest": "bdd7f5960912b77461e3f19f2b93652d" + }, + "countVotes": { + "rows": 5732, + "digest": "c0fae635b43e764a2482a55c3f50dd56" + } + }, + "verificationKey": { + "data": "AABvj1TjS95sAoY8puZRG2h4hxjs9c5enwfo4vZAQC/COWHgEjNupRIxb3LVxaRU2mkaG94By4OqrJ3M7YXNs4oiAhMdOuU5+NrHN3RCJtswX+WPvwaHJnihtSy2FcJPyghvBVTi2i7dtWIPQLVDIzC5ARu8f8H9JWjzjVVYE/rQLruuq2qUsCrqdVsdRaw+6OjIFeAXS6mzvrVv5iYGslg5CV5mgLBg3xC408jZJ0pe8ua2mcIEDMGEdSR/+VuhPQaqxZTJPBVhazVc1P9gRyS26SdOohL85UmEc4duqlJOOlXOFuwOT6dvoiUcdQtzuPp1pzA/LHueqm9yQG9mlT0Df8uY/A+rwM4l/ypTP/o0+5GCM9jJf9bl/z0DpGWheCJY+LZbIGeBUOpg0Gx1+KZsD9ivWJ0vxNz8zKcAS1i3FqFhahkJHiiKgikn6Qig5+Yf3MJs0WKSNkCkgW2B48srVTR9ykLyO+68NiWLEnLXvJd+rmUHR4K92whqctZZ8zvd2+5u+b62pkvFqqZZ9r24SMQOe9Bl2ZfMew2DyFLMPzwTowHw8onMEXcVKabFs9zQVp66AMf/wlipirNztdguAIWsm9MH1fuII+oVjCCExBLbainDfNS2d3U0KQQhNW4w37+SD3bre/ej9EV8CTnEylQh2BCDD06ezujpIBdJoykc4d5ts+btlepIrTet7yJK5rlsFQfJGzaeTz9BN+g+C2ZK8B+2a2Qrz386FvB+elJAkJ2/Agn35oBHB2HobDkF6sRfrXOdH5l+QV7vR2v385RKRtfnmcJeUQcpq5/JTgVwagDJ/FarTN5jFsrBBRTeW3yZ5/CfVNA7NNWxoKhjBaHVIhn/fLT5sFLYzYdCx/uTsusyZmE2d6iqnLS+j1IXNJX/zR0ZD3aGuoUc4MaFZQnN5om4dfpbloe4Roob3BuDhBHTKoYC+nVsyEvDRyiYLEOjJ45/bSwTCfwngYKtNmo3sVTvQ9mqBf0cLdBCn8skp3S/gz324TFm8iJ+t8EWUV1ed3Ufz7Vat2G8Knw9nrdLz4J1fFB6IuGiPsoACDlErBYAPq2aMLeB7ZTseanT/PrjC+g99tjnInHJgwSiHEKg0bN3ajSNpLlFITHOPnADYZZS82SVsP5MGZbxHUYns7lvh5b3Ibhr05lgrwQTtFXlBX9Eq9NvOWlHv77RMTYMqH5O4Df/c6DNekL1d6QYnjO0/3LMvY/f/y1+b7nPHI8+1Wqp5jZH8UsuN63SSMdfBEe6x46AG/R+YS/wH78GKekabWu9QQnUJdjXyXiqF4qRebvfcmpQz91anvVz3ggBqCv4sYqCIvP0ysDtMdi36zFErV+8SdUu+NsPDGvdPSCGdLuC25izxb21up2HORmlM5R7yuIW3rCiq8DeLD0OHjqOBZ+IEv9zEkb5fHTJvxoxnZlArtZSBpD6iIDPVDymuK+BsOggZav3K+TytjeD2Gcld5NfyRISFWUIMkZNFQRL8AQpET6RJnG1HSW0CaRfNeomtjCBWIr85wFCrp06j/D1J8B3EyhloZLJJ6ywxt41smXVugxA8LRTO+6lVBOBF14jHQCCUl6u7uiWCe1z4/bC5wQXPwWSljp8NVU8Erp1U9ModNK7W63Pkh0efvgSD5d0nLzbfa0jTdxZ1JkfKsnvYk43Ed+vmXooHZhUeZAIX8ZCizhb1Gfvm02JFwxYXmiYAOp5wkGzweU2I5zo8r5yZFI1r4XibNQs7eAfKGRv3gh8/EuLkX/bdettgPvNsI8ndpQ3kL/V8W2PQN4/hjC9AKCYBeXQG42bRncYZdLe++R2KA1ZdPDxQPF3sxUIKhzmRWqbozrtv310Maorwv6eZJjldlCJwICR9QgcDwDuNj+UFJnX3RWsdIWsUbI1T4wO0sE2sBiMX/OqmiGJEAnBegioistlFyfRvm54h+duNOl/ol1Fva7NoXvsL/wThAWUly7bnc7/Al2bBQlUrmEX46UnKXzYntkZDee7Lx1u1BBkJAj/5BH1YZOPmMCh498rBUiHmc+4uQqebqNSHdOSgC39ESss4u7GNhWj3fi9XXta6UT9wapEMGq0WTg2Kry6xNP2YZ5X8eaapRQc/KzYgz9XjQL6TKpqNuGEbRlmfYvIuoFbnOkZI7RYoGp3YheMs1pQErwOxLzZa9W3Okwx16TSDwPLR0xMdAyogMrOdKN4JSMyNnmOaoVf6PkN+K9fz7RuHtvgjKpuz4vsK5Z2wRneqPrnfu6PkgHcRQrd0SxqCbN23Z/yp8qOcN6XU49iCNEBjztT00tolQ9hCPMSE/eTZ+ioez7m3pJFVks3T5Rk/e+6MeowJWIOv20x6CPS9mhpr1JPwdNFrWdgs19VsobntCpF/rWxksdrYyk=", + "hash": "11502066611182404086928452966081102834564699929416361079661334997360964242280" + } + }, + "Membership_": { + "digest": "19c484872fa6db7059e3bf8e2be9ef8d7f14d8b17bda8cf4bffe7446420d8b5a", + "methods": { + "addEntry": { + "rows": 5530, + "digest": "43a15de677a05d842e810bb07ae3cc99" + }, + "isMember": { + "rows": 430, + "digest": "c77a92337b58bdee792dd60f826a4229" + }, + "publish": { + "rows": 690, + "digest": "f4834e50c33ccdd38d71492f147fc11e" + } + }, + "verificationKey": { + "data": "AADnPH7zPue347Wo3oNt/8b3xHU8uVKkn5XNRRDPiY/KH7I1DN1b2gilH6Y4yyPwl6mp23vZt9MFl+QMJQBTvcAahS9xkBcfxRTAAMBHXhf8KDkK39AalVocKIrfWMV0MSShinj0bCxPCc10K0cya4Voy8fud4+hktDOuwjaAstpEJSbKRHMIki77xHmJWlFUYdkgPg30MU4Ta3ev/h+mcMWmofyhLSQqUbaV6hM95n3Y0Wcn2LRNxJP8TRwHndIcylleqPsGMh3P+A+N9c32N4kl29nreMJJdcUrCXK90GLPAFOB9mHIjKk9+9o3eZc3cGQ+jppXoN3zwO91DeT/GYvXqCZTAudLxIwuJU11UBThG5CKKABa9ulQ1bYGXj9Eydy0vPxfojDeFrnKMi9GKSjiSMzmOLbIw7Dt+g9ggjsHOKm4039zdOyAgYVvlUxrsxWoHR4L0925Cxcu8aWyQs2GTmVl73Fasa9dYaNrIkW0VZsPGp1l8+jAdEvbsPXrT+qFXBtHaN45PMWCyBx0TKaozETCmv0kA5KGTzesYQCECPQ8F2DM+oXz8xly+z9/Ypt/Zx9NvF7wute/1s6Q/QuAEBj6Xgh96wCSx3E+WDvhO+CuugRNabNIYHRf/i4YXEwSzCpkAPIA+MIcPod69AJBi0OH4b5evB/KXRVeKfTJBK2vXswFt90jphf6jgLtFJULrvKVg+YCMNM/04QLTGcMmjjzv4LciQ6IVXth7zhVKxfL1/2peC0r/ZrP8k+Ox4LEBXWMCQE5kfK476bQgrLeKJfQ45PZfgB688DGwaYAxWbcxBV822/aAsA55ijFY1Xf7S+DiytY4a/u0bellKMDUQqTOq9VwmbDv868zXscUwKpNVR3wy2En/q9M/HJJc4BZyuuQvlQSR59m0gL4hKHf5Dci/YVvM6ACHmg+5SxCr1pUNKbyy2lsIa5Ma40ZmsTpT4/lQczmGENQSQXA9bFibT0Q+Vj885p9heLOCCXyAujC4DhAdYmT1MQ7v4IxckM8DOV0J4xnK/VetxtRLpONoWTk2hGJAQhQYXNheuvgpL7hbzO67/5TjcIvqHZNeQqogQYV5E16AQmTz/ItP8IYho+/YCDX2dIxLEEAv8vQBTY8yuUgpMco39EEq4b7QmTYEf/YPzAJSjHnhiFYH+zl6DLsMFBPFeXckAlX0dYQD6l24QrqQAp0ebGEbpXqv21bhlr6dYBsculE2VU9SuGJ2g6yuuKf4+lfJ2V5TkIxFvlgw5cxTXNQ010JYug38++ZDV+MibXPzg+cODE5wfZ3jon5wVNkAiG642DzXzNj67x80zBWLdt3UKnFZs9dpa1fYpTjlJg8T+dnJJiKf2IvmvF8xyi1HAwAFyhDL2dn/w/pDE2Kl9QdpZpQYDEBQgCCkegsZszQ+2mjxU9pLXzz5GSoqz8jABW5Qo3abBAhvYKKaAs6NoRgeAD6SadFDbQmXaftE+Y1MVOtjnaZDUBdwahWiJMlkfZpxW1aubEc/GSX8WzCZ8h9HeakcRc7kcN0CR8kmfER3eiZ2JMbt5cQl/afNjwGGAmeXzTaR34AgFjiw/RlZJkhYm9jyf18M8yP94QGBMxd6Y6wrNvOmJHzEnp8aitJsDlZklm8LKbjumlSbLcbBokpIDhFBBKfwP2qsQX7eHLCZ/3mztoFKoIiYXgrHWG8m2SzIJ/ljn6Rg7AxIsPjzZyEw1eXAOC7A1FCT/757ygMsnk+rLlpDTBYLmhJtQdt61MQFDi5BuCmQ/PY9C/74/k4APl5htiNcCZty/1JElFwjuCQFjvAiMPUMyqp7/ALFapsTZqhSs1g6jd8uhuJoTNEqLDvKUUbs0kMvGy8BOG0YXNxmNccabGwBzxmijv6LF/Xinecl4aD8FCh6opY98TJnOHd3XSYL1DbLqmmc6CXEM+g5iDGnXr/CkI2Jy37OkF8X03jz4AH0Yj0+J63yH4IS+PrNpKZEXKh7PvXNaLGGKsFcKEi63/xKPKH0G4RzvFKbkp+IWqtIYjMiwIJMwzmfS1NLLXqqpFiD364eFcXINR2rrDKcoTUp1JkVZVfXfKwaRUPWSGFYIYMtwPh2w8ZfubAmXZFpyzstORhFyg9rtVAAy0lcDhQwWVlhFFkR2qbdoy0EFLBrfKqUIkd1N6vDQQYL1RGaTAv/ybregrJsFo+VP3ZatlR6LnKYWp1m7vPkGm3I6Pus/mvp1k10QGk8nhFuR31DjsG3lzZ4gXSs1oSv0qbxD2S6g5+Y6cPbITEGX3uQjsunXnQ9PHd22Mk+fqbDakTiCJh6aFqqPNShiAXkGSuC1oXJHX3zqnbn75dWO0UVhBNAbjYkSnQeyka1wnZb12sR+PlRMvWQVcd93t5L/FiE0ORo=", + "hash": "25831825211281843080287081490020420935981614412152371854916701329313682426902" + } + }, + "HelloWorld": { + "digest": "1cc4745c7a9a21c6d0be2aa1b9416106227efb91209af60c7fbfb1d3817533d", + "methods": { + "update": { + "rows": 585, + "digest": "88c83c391be40dc26611ad774c74a693" + } + }, + "verificationKey": { + "data": "AADhxKnCltp91j7lM6RAu89sefjgjm48WPsvw3D5wAJbEiIqMl4W9F14fHz2jMZFsZu5xXDmJyd1HCpIh/5tR2Y2Zxrm8etZg82vugnGjikslSBGhtwipjyp0Qo5LfTmwzO6qmGnWieyABMDgCGqCmwJxW2g+adyu9hhmuse/WhnJSe5NhoztPWGnxmDEmL0sATa/AmOl/TZwB9kV0MPU8I6nwZkkoHL2ZZz6FGbchI0HeCEViyzV+f9PMIPhDiQlyfLtpfVAyIY06XCc4grEuoHcIf8lic76syVofMXaoIDE0mDadgIeIR1VrdeEeufn2VabqLDTAs8gWTlv2fCeeMp8I6db/g6R2avb6C2WoKszKThWN6K8SB184sqMuT3lhFWAIno6nX2SUuxQVWLMT2T6a3DAC5C1qYDN3WlsH2cFlk2VoGAmZ3cM+LDtcGgSWePM3bfiGr3lZWBaorws4QFIB40UOKCK4BWvbl7fcSRY1+3LR1e04nf0kxrfIYZ3wapKc6XGVKPJjpLvlBEk0AQPNXIQTdRWTddWJUPA5SiEP04iqgbpetZqbx82xQ06Dr4xtOP0vwcZwGIY+LGndM7AC17I6rAz0NAplAa7QN9KIYbQDI6WAHc349dHOT1yugPULSA1pYBwimFHTmqpCtGeBAsPJYv/AVFJviwhqYcCREkCdYBNnekfSPubsOHugj7msm+wpsabOGPdol/QHJNDAlUk77WjZEpQmxHOiE66K7r77+16lRbgbrkbIXxAXsKVrMzwyMWVfWzHqZkN2FsAMBYWJO1x8lqY4XukKRCnjtelAq+gZ4ljp7YlQFnM3GLA3oGCvQyIqwePiCWoZpJA4SwswCa2uNPZ+7PMzZbMzddVkwj921337rtBzP9OYwXIe6aPdqI21kTf4KBc1Nu+071d484vfsxBEpIj2VWET0bk94MYH2w1ThMqEOjvdLH7Rjb2yq6S/pHxzA0+6u7PW8S6rE/B0hQj+15Yefwb0l3QTzgj2x0JDSZLXfM9v4eoftDxlncg0WjL/iJddg4bbTPkA3ZmeeX0OT0grzrGAE4S7xu26QxRvp6KYc1u9MwWAbwhttKpGNw6kxsVnyONBSB3dphR1+4/fKCaDZiM7W1Ehtv7L91QEgycB7lkOo31xGBWvFCmOja1I2j18ANyznh7ppyfrE0KDk4f6tL+TyakQKlZim7WJBz45cwQ/Y+wzWQL4N92uh2awCSzau1Kn4/CBjw+Oq7HSz/k1oL0J5PGao68tAiQD0tOcEhUIsHD36inwHMxroxJVL8zLKdkAE8BM7WITJyXL9FWp5q/BAVTIML5L7BEZzQkLVIiVW66mRV1+jKE4Y1u3exah/mJR43GZoyn4SNlnjtlQG9S+VLerddhdyF3qnc3t3o8ik6x/0qD6g+IBuY1OIl7EG6UUp1avg2YGgPiOrfGnX6LBrtboOkA0iWI1TRushJ744l87XpZKLfHr90QzBQkgAPCcEAG08LLim/FcnS3kAjfAE0lxyjrSMY5s1+SbnnTlAMOacaPE09xwW9FDvwkV3FNaBTX7srLovC9VgavOPx5QN0gP+hy8/zdnwG0aZvLCDNHa2NOxe8q35gdA42Ru+QMdKLYIuw/xGXYjzoOdYr/WDYjNQzzDKeHwzx/vqyFLkNA/mMzaNMxjQTlfrFnuTzj8oyY7EuUdwif18wcTsqXAMUVhRtN690neD7VWL63HgR7q5p3BA5J6NltbjPXNJLP5dMTi71vUoBndyX3XlcObmIkZKa2YD927QsfATwyg4mfkcjMw1nboag8T9VcExC6cY5nrNcBvXmtLz3pUiTcSj/UgCgZWa0GOGBS+6P5PsrtGDsvLrmqA2ed33hv1UiHwDLSTXvh+zFNDPSPTnoT53wP7DE0FjilgeWv+F5o851CnFU50J/gtMwWrrmw5bjAKk+Dh39+0B0OfLxG5DFjnwmv3JkQzrnB+GFZ22PEHdZZ9PiP6C7/kMFaB6+V2gvrDJ6BTesrreFShEyHJKvtdPWeE9WWm8GmSY6C/PjGryIERm7Z2za+3ze7ZEUQx9RxrzYnNrGSqGJdtDNk0pHDG4khS3+AfieLdNKHdr19IfUDzCgCAvY5ukD0N7A9qxW6C7VPVvqWINu1zU27wupHjnu85MW+cIs5RPIrVS6Xry0Api3cCFuPK2pfEpmDy/K3AtLptp7NDJRcD6ym5K26rcdle9voOz4VLuCe98Z/rCV2W8fPfBGjPWMX2pjZL78oDFdbwKSBi1ipV79IKNCY6d3kSZpeyFlJZX+MBY5NXKLHygje3kDLDQ1eBKb4ORKOrEVw/Mxp7jGrAlG0Ac7y/s+gkC01hulcJ6CIkgIUZiS/TYfsH+F3oqmNXzue0jHLwQ=", + "hash": "20121206666868281202734555532052898650947130005414349312118747377615715651369" + } + }, + "TokenContract": { + "digest": "169503b57cdb814f1b0581d8bbe3d9792ae26c8472cac543566301dc098a05fa", + "methods": { + "init": { + "rows": 654, + "digest": "ffd967e8be9e5e221c4d1c848751e7d6" + }, + "init2": { + "rows": 651, + "digest": "9392237b6907f8d3d4a97d6102fa8749" + }, + "approveBase": { + "rows": 13232, + "digest": "d3d5f2403ce7225cdf3419a587795499" + } + }, + "verificationKey": { + "data": "AADnPH7zPue347Wo3oNt/8b3xHU8uVKkn5XNRRDPiY/KH7I1DN1b2gilH6Y4yyPwl6mp23vZt9MFl+QMJQBTvcAahS9xkBcfxRTAAMBHXhf8KDkK39AalVocKIrfWMV0MSShinj0bCxPCc10K0cya4Voy8fud4+hktDOuwjaAstpEJSbKRHMIki77xHmJWlFUYdkgPg30MU4Ta3ev/h+mcMWmofyhLSQqUbaV6hM95n3Y0Wcn2LRNxJP8TRwHndIcylleqPsGMh3P+A+N9c32N4kl29nreMJJdcUrCXK90GLPAFOB9mHIjKk9+9o3eZc3cGQ+jppXoN3zwO91DeT/GYvXqCZTAudLxIwuJU11UBThG5CKKABa9ulQ1bYGXj9Eydy0vPxfojDeFrnKMi9GKSjiSMzmOLbIw7Dt+g9ggjsHOKm4039zdOyAgYVvlUxrsxWoHR4L0925Cxcu8aWyQs2GTmVl73Fasa9dYaNrIkW0VZsPGp1l8+jAdEvbsPXrT+qFXBtHaN45PMWCyBx0TKaozETCmv0kA5KGTzesYQCECPQ8F2DM+oXz8xly+z9/Ypt/Zx9NvF7wute/1s6Q/QuAAVGOlU4R9jb1g2n5sHjS8w6IY7APH+wYhtO2yGLBNARpdul05+wjR4Ro2lRaMDQWgGYLd5MP6sGuF56V09dYTZW9tFMt+wjpebqrgW1oGsxjsJ8VwDV6rUmjuk5yNWvHwdtZ1phyFP7kbyUnCpjITIk2rXgPyGdblvh9xcV+P4aEBXWMCQE5kfK476bQgrLeKJfQ45PZfgB688DGwaYAxWbcxBV822/aAsA55ijFY1Xf7S+DiytY4a/u0bellKMDUQqTOq9VwmbDv868zXscUwKpNVR3wy2En/q9M/HJJc4BZyuuQvlQSR59m0gL4hKHf5Dci/YVvM6ACHmg+5SxCr1pUNKbyy2lsIa5Ma40ZmsTpT4/lQczmGENQSQXA9bFibT0Q+Vj885p9heLOCCXyAujC4DhAdYmT1MQ7v4IxckGQcIsUdQua45I9VLQETwR7ToWEOhPdTHRmOMqjojpD4wRegxCjNvjlhA9xLmhCjI+wZtaWU7L7j6Fhs1awVxP1nW+UfKuumtwteEJZpUyx8bpmQAVTPAJfilMUSJZUg8SPfGQiDIPhofMnR7TFYrKycDwoLqPpWdZCqIOU5eMz/6l24QrqQAp0ebGEbpXqv21bhlr6dYBsculE2VU9SuGJ2g6yuuKf4+lfJ2V5TkIxFvlgw5cxTXNQ010JYug38++ZDV+MibXPzg+cODE5wfZ3jon5wVNkAiG642DzXzNj67x80zBWLdt3UKnFZs9dpa1fYpTjlJg8T+dnJJiKf2IvmvF8xyi1HAwAFyhDL2dn/w/pDE2Kl9QdpZpQYDEBQgCCkegsZszQ+2mjxU9pLXzz5GSoqz8jABW5Qo3abBAhvYKKaAs6NoRgeAD6SadFDbQmXaftE+Y1MVOtjnaZDUBdwahWiJMlkfZpxW1aubEc/GSX8WzCZ8h9HeakcRc7kcN0CR8kmfER3eiZ2JMbt5cQl/afNjwGGAmeXzTaR34AgFjiw/RlZJkhYm9jyf18M8yP94QGBMxd6Y6wrNvOmJHzEnp8aitJsDlZklm8LKbjumlSbLcbBokpIDhFBBKfwP2qsQX7eHLCZ/3mztoFKoIiYXgrHWG8m2SzIJ/ljn6Rg7AxIsPjzZyEw1eXAOC7A1FCT/757ygMsnk+rLlpDTBYLmhJtQdt61MQFDi5BuCmQ/PY9C/74/k4APl5htiNcCZty/1JElFwjuCQFjvAiMPUMyqp7/ALFapsTZqhSs1g6jd8uhuJoTNEqLDvKUUbs0kMvGy8BOG0YXNxmNccabGwBzxmijv6LF/Xinecl4aD8FCh6opY98TJnOHd3XSYL1DbLqmmc6CXEM+g5iDGnXr/CkI2Jy37OkF8X03jz4AH0Yj0+J63yH4IS+PrNpKZEXKh7PvXNaLGGKsFcKEi63/xKPKH0G4RzvFKbkp+IWqtIYjMiwIJMwzmfS1NLLXqqpFiD364eFcXINR2rrDKcoTUp1JkVZVfXfKwaRUPWSGFYIYMtwPh2w8ZfubAmXZFpyzstORhFyg9rtVAAy0lcDhQwWVlhFFkR2qbdoy0EFLBrfKqUIkd1N6vDQQYL1RGaTAv/ybregrJsFo+VP3ZatlR6LnKYWp1m7vPkGm3I6Pus/mvp1k10QGk8nhFuR31DjsG3lzZ4gXSs1oSv0qbxD2S6g5+Y6cPbITEGX3uQjsunXnQ9PHd22Mk+fqbDakTiCJh6aFqqPNShiAXkGSuC1oXJHX3zqnbn75dWO0UVhBNAbjYkSnQeyka1wnZb12sR+PlRMvWQVcd93t5L/FiE0ORo=", + "hash": "21038204875622560029423025114277359110145981521195300124546757304990896411581" + } + }, + "Dex": { + "digest": "1ed56246c01d984270b8baccc1e94ba9c1dcc2852fa12323fa09a4ea9b8e243a", + "methods": { + "approveBase": { + "rows": 13232, + "digest": "d3d5f2403ce7225cdf3419a587795499" + }, + "supplyLiquidityBase": { + "rows": 2841, + "digest": "26f29fba6f5bb72a58ff7bd5f1f38a5c" + }, + "swapX": { + "rows": 1512, + "digest": "c9cc2dd606b458fcab809c7fbf9d5b07" + }, + "swapY": { + "rows": 1512, + "digest": "6c43e323161d2f005183cb2319bdfd52" + }, + "burnLiquidity": { + "rows": 704, + "digest": "5fd003d4c61cbe78663827181fa65652" + } + }, + "verificationKey": { + "data": "AAAquFdEgAiP0gVQOFC1AYSsV9ylHwU1kj9trP0Iz00FP8zx9+7n59XMLqpjue1wA4VfgD2aXaC4seFCHAfaZwUkB+uHOnxXH7vN8sUeDQi50gWdXzRlzSS1jsT9t+XsQwHNWgMQp04pKmF+0clYz1zwOO95BwHGcQ/olrSYW4tbJCzCu0+M5beMUxHl3qo9fsP2UE6wUyrUH+bkM1NQAsAz0p0Kf7RXT4K2tC3hCxybh9Cj1ZLfvzg03OR4HBo61jF6ax6ymlATB4YBL0ETiEPTE/Qk1zGWUSL2UB6aY45/LlfTLCKlyLq7cR3HOucFfBncVfzI7D8j5n4wVqY+vAI4cf+Yv7iVRLbeFcycXtsuPQntgBzKa/mcqcWuVM7p2SYRrtKdX8EKvOO6NhfLx4x0atAi8pKf+vZR76LSP4iOA8hwXvk6MNvPt1fxCS96ZAKuAzZnAcK+MH1OcKeLj+EHtZmf40WRb3AEG5TWRKuD6DT5noDclZsE8ROZKUSOKAUGIBvt7MpzOWPPchmnromWEevmXo3GoPUZCKnWX6ZLAtJwAszLUgiVS8rx3JnLXuXrtcVFto5FFQhwSHZyzuYZAHYRlNaRh9gbonUumhMwjYoNMCkJe2/fcuDnlq6RkpM3J+AdtxyYUZQfIcolhNEqSxn80Iu98ZOhI69IGNwtXyJuvC7XICgbTBTdkLN57FdxSKP/IMK5/VNmSxUR7ugVNqDtkCXWhCrJYDo2sPyDzNqbExPmG3u6t74md5KoqHYCJ5M/KjfmCc2/EsnV7Mhax350ZtrXdzh/HWIWzEZKKxcbERFbRtf+fkMOOLNpNov1FEFvKOU612vDOIbrVHeBN9mwuepUrJctcfgLc0Mi3Sxs3+NA0I74qm5ktjmplDwgUtKzIs3IrVFv6b1pg/J32HmwNzJZw2fYzpFE1LDjBSK/SX3axwMy5yEd8+jl4uAdQZpa9UQQIHu1Y1ZMgJSDDicXz6D1bZMA1Q2/lU+8AYbldgQVmlLq/lzr63krX+AMeZxKhAhBBRc9208Y2phRSSxjTE2tuhy9QQlwmAFh6w6azBTDyh6qP/8orVheox5xzYGAynSpx6cQN5QKN/ldI3lVzcQAJ183VU5rXeXL+UsuVkkSto2fGFExQqa4rckrOxEzPkQ4IDUSOqIOTuisgOj56kN+9hduXXhizBDRjiT59l19FcR35ItoigIxtMfkv3rdlCOeBVI93oVl5esiH8AvYGHhulWIvrNfKol3Viir41zv4qMBOcQg8+ygqjwqREU5+qiYeJlQ2AtT0/PVeZWg4mHC39uz1Lld3N2hyyxRo+Z0nC/8220uuf9gAnQ+JFixgyYW0NowUtuFj+uYAV9Dh/Zpe4LyAOkU0kBW4CEuOxNr+gz+9h0BoPfBHlMuuQAUc5L8uMunJC7uBKZiL+/tT1ZGfyIuqU47fEP9Hghxmip8v7gpf+4wB0MVUUwav9QRe9g88ER1HcJPqYb4EIOc2kbYSX75bT0mAFqR8lwZrj6lbQtNS0QQboG5fzoyYGi8YnSXhC2T5fFDpGJ319GHUsna58o5wk8LMwKWNTxq+FN6XiRgu0BFOrtG6MtT1OxYE9Dti6WatGDsWv+KMLDHjxUK1bhiSRnvkWYNcnuDJ0Ry+PRGHNUijVU0SbchntC2JHdhwKbwIofwKHE8HhvlK8FgQ1VOLDioA26UFzr23LpCTqwSJ7/sAqttNGcPR8MSeeR9TQvXNYQPKrA7Gh720X+7LD6BuHdy4vkcr9EKBU0ccUJ2ABBiyPdji+AgEbUCL/wrp6/GX8pui5YJGWx3XmIFj/RnYS2Je5FZ7w74JclD3XhLUo5Dhpq5RznHplpLB9mNdZdm5269US/XCgC/ZKyUxW3+0ajdBY1cLzF6qglitaYTp3MVUENVOkACM2RyKw6jIK2Leq3qLp6AUz21VXj4WznZcdI8MXqT9v8HxjXbAI9dtbhLRZRpJmu/129vrVmwSTHvsVoA7vXyYh/iO3ZMcy+D1x+HZU6Q/oDYCicqOPHxpSc9QGehmNyeGzI//524Gz3RudkU7s6MPdLWqZrieRTnWsTIrCDieu4ValfP8BFz7asYUv0t9jMWpv3yjbY7c5h8N/m7IUXwTQCzFpjPV7HC72BjVwPaYqh5/oAQsSNcv5I3c2GsCGj5C4hFFoT7eWfVtu/6ibQl0COhRDsegnOBtZ7NGfybI8IIO/4yrgel92bypb3eSxeMvdE5wzURluGDkBVVIACD8C5W1MzqrejUiiTfc3mkLhQ0xKRRhT0qqkmYWlbGN5hmMOA9YaYx8OFTgMys1WbzdidWgEkyvvdkWctGlges6eg/lJE61tJ8wGxvJfKtpyDW/2MRvsnO1+2EXIQ2eV3hkxg=", + "hash": "11995787401471485847112327267221844756507727092913123363897384059516663907637" + } + }, + "Group Primitive": { + "digest": "Group Primitive", + "methods": { + "add": { + "rows": 30, + "digest": "8179f9497cc9b6624912033324c27b6d" + }, + "sub": { + "rows": 30, + "digest": "ddb709883792aa08b3bdfb69206a9f69" + }, + "scale": { + "rows": 113, + "digest": "b912611500f01c57177285f538438abc" + }, + "equals": { + "rows": 37, + "digest": "59cd8f24e1e0f3ba721f9c5380801335" + }, + "assertions": { + "rows": 19, + "digest": "7d87f453433117a306b19e50a5061443" + } + }, + "verificationKey": { + "data": "", + "hash": "" + } + }, + "Bitwise Primitive": { + "digest": "Bitwise Primitive", + "methods": { + "rot32": { + "rows": 31, + "digest": "3e39e3f7bfdef1c546700c0294407fc2" + }, + "rot64": { + "rows": 10, + "digest": "c38703de755b10edf77bf24269089274" + }, + "xor": { + "rows": 15, + "digest": "b3595a9cc9562d4f4a3a397b6de44971" + }, + "notUnchecked": { + "rows": 2, + "digest": "bc6dd8d1aa3f7fe3718289fe4a07f81d" + }, + "notChecked": { + "rows": 17, + "digest": "b12ad7e8a3fd28b765e059357dbe9e44" + }, + "leftShift": { + "rows": 5, + "digest": "451f550bf73fecf53c9be82367572cb8" + }, + "rightShift": { + "rows": 5, + "digest": "d0793d4a326d480eaa015902dc34bc39" + }, + "and": { + "rows": 19, + "digest": "647e6fd1852873d1c326ba1cd269cff2" + } + }, + "verificationKey": { + "data": "", + "hash": "" + } + }, + "Hashes": { + "digest": "Hashes", + "methods": { + "SHA256": { + "rows": 14494, + "digest": "949539824d56622702d9ac048e8111e9" + }, + "SHA384": { + "rows": 14541, + "digest": "93dedf5824cab797d48e7a98c53c6bf3" + }, + "SHA512": { + "rows": 14588, + "digest": "3756008585b30a3951ed6455a7fbcdb0" + }, + "Keccak256": { + "rows": 14493, + "digest": "1ab08bd64002a0dd0a82f74df445de05" + }, + "Poseidon": { + "rows": 208, + "digest": "afa1f9920f1f657ab015c02f9b2f6c52" + } + }, + "verificationKey": { + "data": "", + "hash": "" + } + }, + "Basic": { + "digest": "Basic", + "methods": { + "equals": { + "rows": 4, + "digest": "941bc7a3f1bc61dd9a031a90eda35257" + }, + "if": { + "rows": 4, + "digest": "8bb444eb77c99f065f108e1dd124bdc0" + }, + "toBits": { + "rows": 254, + "digest": "49e0fdd67c13bd800f641f2375301322" + }, + "assertLessThan": { + "rows": 24, + "digest": "f7979c1aef4fb5672c7bd77ea927c10f" + }, + "lessThan": { + "rows": 38, + "digest": "eed5490ecb8d1d6a8e9d3699cf5e175a" + }, + "assertLessThanUInt64": { + "rows": 14, + "digest": "caadec00ee68eb4510555fed0dc4f0d6" + }, + "lessThanUInt64": { + "rows": 15, + "digest": "87c70877d218c093b8ab5e2848a44611" + } + }, + "verificationKey": { + "data": "", + "hash": "" + } + }, + "ecdsa-only": { + "digest": "39205ab5c3c80677719cb409d9b798a8f07dd71fde09cef6d59719bd37ecc739", + "methods": { + "verifySignedHash": { + "rows": 28182, + "digest": "e75305f0564b7d2a8d5373c4c4b4f45d" + } + }, + "verificationKey": { + "data": "AAD0TiJIvE46IEuFjZed3VZt7S8Wp0kRCJXb3a2Ju7WUBunBgHb4SXvz1c3QjP2nd1qSYoUr66taz9IKVgu+5so8TiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cIw5azWYCxKf0pWJRDHbLUtJrjnQT0ATHD77rtbgLedcfFKfCQS5oAbF7hIhCbAsm7wMT+9+ZX8M5354UKZ03NiLUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAEsbnN0PWue/xT9AP6xOPAFOAkpVcN7OBBwHz4tGSNE45YYNv6dFaIni6I1JOcpD/ygW3iEWPjEa/5AyqyQIiScgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6FGIs8TlruzZJRhz1lOLvl2FPvrUsFtz4yWTPjbT+VGsKuJFPvMuYybxq8pGyWVQN023uObDel47krlcQoH4MA/5akvJfixdlqlb5ZJMUHOPhu2znlXLaagLnt4k2XUDPIkpLwGgWblwdtFJNRUTCLd7SLqpEOOzqkinoGAuhQGvDR5DLlenSa0wQ3PXdv/C9LpDvkzJOLZs+/ZePd4YMI0+WuP2+6Xas4aNM+4JkNuHF5uMDcxgWID4TUy7Vdlzm3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMjvxngHnci+aYJZ6J+Lszh5zgo708vzO7fwaxC0wgd8anH3gFrbFnOg1hkmmoUEIgIwXh+ynuoZPOaoKNXNm1jOl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "16699327930281854286387542861061814386834981380346591095064575459065082391965" + } + }, + "ecdsa": { + "digest": "15029f011b1ee3a18dac72dff097e9ad244b112c4e928f6dd0231dbd792c2e7c", + "methods": { + "verifyEcdsa": { + "rows": 42680, + "digest": "645fe57449b2f3a8a6715de6a33c897d" + } + }, + "verificationKey": { + "data": "AAClzwJllhsXW4UHH6dHRfysOr2Bv96SCGeRT4Wa3IseIaKiigrgpOaDXTEEjlaJI+fiakjF1+4p1Q0TFxwpf9QVFNjCZ84EnGkie609NhFB8tU9k5Vkoqw3jihdsoJEUy6GK0H30dl/7H1rGxsx6Ec05aaFhiPw6t0jLxF1kj4uIUUtyjzEBEpi1D8Jgw5ToAzE/dLLvgPlMu+gZu6pyyoQxnJ/0SYRd63N82MLWgTkbP8yzNSL5FFaqjZtE5VjvjZin4GWyDB9279EZ6D6avFW2l7WuMJG++xBqGsNKZUgNM4WkUGNfCd+m42hJgt46eOy89db672su0n24IZG9tAsgQl8vPsVKfsTvTWlMj6/jISm7Dcctr1rZpSb8hRPsQstlfqMw3q6qijtTkFiMsdGRwJ6LNukSFUxOarhVsfREQngJufm4IxFpJJMR5F1DFSDPiOPuylEqXzke+j078Y4vr+QRo07YRlsoEv4a6ChcxMd3uu5Oami+D747/YVaS8kLd/3bO+WFpubID5fv4F7/JO4Fy/O7n1waPpNnzi/PZRlHVlwzNVAs09OmTmgzNM4/jAJBO9lRgCFA1SW0BADACrTE0bmSFL++dNGayqdY18NPNOp27Ur/2FRYF5OHFw8E+ceee5pGmcZhHpQxZLdulWAqODKxt7h8zstlCKNjjvPFNzYqZw3swyXzQ3nvZqWU2ARuzo1BgMrvnDgW1H+AMbKbNGU7IYXIYaLfTR9S7qrUbHESHac4wo9J9HmRiU1/IQdyr5LldYkzYtZOrjM4SzBkYYVtpSH7Sopij/TTy0U9CXNle7iCnZQS/72C8kwyJ+BGqpULLkSWhHoj+U9GSW9UgDHZ62jRTzvuZz5QaX/hYOmpNChNMFS1zoDYVE7ZIzVQKX03IDkzHAVJCXggwhQO3NK6OGhlP7A/heM6zgiR3/LlOa8uW4fcow50XC3280SDziK0Uczab3zlYXPPH6KqGPJfnftgwuvcHsRgddOWDVfEH3Q9mAj0y1R1FopUEfGeRRHFZMzqfT1bmgpSgMMgaFYRoMKZrOwePlnhjC1ZETalxD2HthSPCETlN3uhVUxMGj5rk7trt5VSO6NBA2qDXacvJQHRIiBHfPZ3G52Z2lTf6OGg/elBurqGhA2wdDAQrBIWJwiTClONbV+8yR/4Md7aPi44E4XICLpHhE5hzko7ePy9cwh3oXy3btBt0urRwrl4d/jhHvoYt1eE2inNWEOYdlkXFUDlDErwOpFVsyQon0G25zNLAcVaZgdJLWueU1y3G0XkfHRqMZ8eV1iNAegPCCNRCvJ6SVsSwcQ67s45a8VqFxSSW0F65bDCI6Ue3Hwpb1RFKbfSIJbPyUrVSq5K99wUJ01O93Kn8LQlrAbjHWo5Za+tW0a/+Qlbr5E2eSEge+ldnbMbA9rcJwZf4bT457dBXMdlD7mECIDZtD8M/KLeyzMEinDzPfqnwZjU2ifxs6gaJPXOQAWPzbCm/z2vGlRbXDGZF6yTbLTdjzviuPhVtb7bzsZW2AYC+TlZqb4qm9MAVsH5rX3OZmvvmw5oRKeSj+FFD7uSRwfutDGC99i93uptU8syL/8Tr8xU3atxITlSqHqG+rVGWdLO9i3iq38zXgXbvZacrc3CMF5QBIM8yZXNslXH5k39D5SqubSHBWTqAJ1I0heOjaIHQGLROBYLn178tckBxfKQ2UpyfkvMw1Waw+fp5f64Ce+5bmYyZr6Dhmw/xcoAihjUsEqoecrLuGPp6qI4hQt9qOnVrAxHzwwtJGxcqoiCbe1mgz0fxMCt/i0z3ygdqAn20DKPHuBdqgVUFwx2T7Ac9fUCf3RHMq34onrr2nLHc038GYedmlFjoUZStujGwA8tSwLWyuWZTDVV+ZaW92qkhmrACog6NwhR6SEjQgsMRCVBQZzYirZxyulYmcNWH6BUmnLLFsn3GbS40xUr70gujEPnjZUK/ExGRfUPOfrYYb8mAciE9nP8OeK/UI+zjJy6Qp8mMroFw7gVHCfDtKTeQFt4JV3zubGsD7jypquHKCqPewhgn9tZ1UIsKIQB7+hBwDHzhlOZ2FfR4eLwQkO8sz275tpjHDAqX/TBWWRVg/yBDii0CWN4bP8UuX36jZKZboJUxIkM1xThiGZM2/oMbe5cZyjgrBR3P21wiDHAAlsHkaMfJgkVLqvZOw8hflKRIMa2dEYo5voD6aV30sATHQLoV0o+MlV3WA38RA+23Jqt1g+UZ7ReAuDP88jXhqWFcIvWHrJG0oy+rpAPQU/38vhIxbl//lirsirdVK2LrU47CC1f9/pRi07vTnvAm+n02dhwriqpwOmI2o2OU4mO0q96pCueKjAttkXgz+NSIJzcwprvNyE9UtKWswmIQg=", + "hash": "24234640217227092235483302942029507829068897711674949436978686588885988704717" + } + }, + "sha256": { + "digest": "38eaa6264b1e4c1c0b5787c06efd92aa7855890aeeafed91818d466f1b109ca5", + "methods": { + "sha256": { + "rows": 5036, + "digest": "c3952f2bd0dbb6b16ed43add7c57c9c5" + } + }, + "verificationKey": { + "data": "AAC1s90ZmoxUiwkznz5WbtpkRWd0zhMNGmqVJ9Br+JOkJ1IL6gnQ0kbSSYzXlwu7QjX8WhU7+8JJW+6fATJ6Rd0KxtxZYv4CjM7SwlG09G52oNZmOgGCilD0ntmby4rzJTaoyCimx5ynQ/oYjslJgSM/1DTAkpW6IIdFjjkmjlOSHk+JWeF2Fu2rmIb/PEcUMfWARMstMlmtc8F/7Gk9jSIXkoINI0AxL0FwY+CqwUtFeUc0LLhvrVkd9JPpacoOyD1lQKaON74XvbnvJdSAYLQSIBbOE0yo7tS1vGTPqDqJGzc1f0+2QhZttE2UEUV4PPEGB6LUFEuQPXK8lXyXHVQTOU+omjpvKHLLs/dQZLTSvvZ3UFqxUvxjSEG9eTPo3Cyt4wXcPEi1b993ePSSxP6njj1SoPBA4BvLCCcvaxz5DegUu7nZDKkC8tuaoNbqYcayaf/8+oZ+dA+Ek1Xe08og1Hz9gB9cfPvgI9EiL2Na2cgiOF2klHHEAj3fORN8UQVzs0ioqLcQeq2tH2UMMtZS4JNcohHUah1T0jKmS3eZDqe+aMuf5qzKA5oKRF2ejRwmsxI161YlgXXpmHs+kCQGAPvX5+/WvgVyy1uIX6426oQoB+0Ni/lWEhFNY+MvNVUGIgPAyvpEafTl8DMfPQkUf0yQCPxRg7d/lRCRfYqHRT74U20AHCGYSahuSFbBO6dDhRCPGRCJJgxktY+CSO+3B9mLtCe2X23FeFsUnaAFlzmsWeKR1tMyPupNsUXPFugubYZYd1EwxUDeBIOLeahcqX78yDDMOdsr0QHNG8XteyXcRXGFUSzTTKNEXdD+BOMY6FfMroKO4y3FffyrZef1PRsu/kVwaZb4UthvhBwet8DBcMa26kISlsI1ItcvsdwST5x1+iLCM4hBcFB0ly3N18uR7+MLHU0AdV13INFo8indyz1XEZCHlm+tKF3LX8Z7G0v68uRmjKRgy/S9NjKPA+M3rbj4pDU+jS4EKN++nKYxXjwdKJXu4XvbUU9ITKM7hwGRsMOqcMyUTbyXukWv+k1tSf9MbOH0BCVgQBzj2SiljlEYePMzBGm1WXhnj34Y+c3l038Kgr/LhS0/P33yPk0OzQ8/vAiYAdVmcHuS+M1etSdnWerxU4E3vC2odvcjNq2yh/VyODIwPt/UPYT1soPpv6M3XSyvpE1kVb0TmD91v6mXvfq23wSDn7ndgLx52m+CGnEGzA/OwwVQnZaFNq+sZjKjOa8ALFNcjyS8IuYEz4XYMiSy/wCl2n2AHVIc6djnQZySvECly6HHJSpWpWv8zvNfLUiWozg4ActV4QzQsd2nagCedaw1z82uWcKLraPboPGke+1dGOqg8OFiBJUBoW/bROBgw2H8WnPIhcofMwGOCrXpMGvA4LKnn3okzY3hTNfex1t7zCpxQC2YRBN0Ze8qOELHTYvOlSwG1AQqZDhC//VDGFvvQ1ZgzsmpnbyNGTGMaCKRMGI0evgxD6Ziitu5k9ogVW6dGSR9cBM0NryOxQl6JcwUXd0twqsLI0pbH7cjlGzWsylGuufx/m77GPRqnysk6r+ibZ4fDBFDumJFxxbS4AqzXLR+nNg42hJbvuxsyZnCRpkB2N9xH3VX8/Il8BX40HdEE/08l3JmL3jn8Bznqwj8z3TqBFMdRGOOq2/5DjwcjaNh9pYRn6bDl/1qVPrJkUExPECrQRymHQ3JERZjm1G+a3bhAJFeb+Ak4D8PlK2RgPcMCS7bBXWuPRh0z4FKGnhZnecNmuWMSvUFsSZcoaFKfyNanX8qMsu+KWtWEbDwwQ49NrfCmg45/WAOQxX8LKMYgUrDpSVdE/bM+JqYpq0AmOHAhoIdlOC7jVMIPI6LEAVJC1PrFQBS3HbH+u5IMQ684sJehPFtd1pjpfboDrnbgfhnjFf9HqS5bG1sR1Dh2mXGXpQ+ni+O3FvEYCEZY+UU9d9XCGwL2OZIhtMi6M6qcnrn11w2MyaZ8U6aZxVTMFvKQ2JLtwVGVnMDuSkNC+iN711acwssgbTpsgHLdVbtFR1oYcbpJHe6a9SFquG+q9qfzT15IzKpBzxn6BVXfhhFGpJRAbU0bXbjOpeceg7vaV7RykTzBoIzAe5aUVAdKNM6fzGlPw16xx7QeOW+LFlOm6HJyxYAZfbpB/BLza4ZhoqmVx+ALUXHFIztgGQK9rzm+jBwiuwuLqdD2W00cbTZcbgKTo48XD6NJ+8T4J9B3rPzht3qbgpN//TyYkfrzAercAa/HCvFeBNXl1slCj8cF/EO6iX/NnIxBkuqmXfQnGUfcFK0LZPsvd7RInaLEYTeA4ZDfChiuw+5nTmrJFOywwOYdIA+NiMfCh24dPYAAwEGb9KLEP9u7/Rp5uPi0S3tuTw67yg=", + "hash": "14413201282771160056563121654578986925263855500702512388303223444020686691310" + } + }, + "diverse": { + "digest": "3cf637b1eb3fb7e15263493b9778078605e6c9b4921976b46e08451e770bd665", + "methods": { + "ecdsa": { + "rows": 28182, + "digest": "1c069d8ca01c3a3aad1c83ea8fc0fa64" + }, + "generic": { + "rows": 6, + "digest": "c23e00e466878466ae8ab23bde562792" + }, + "pallas": { + "rows": 891, + "digest": "e5f666ba87d050daea47081212cec058" + }, + "poseidon": { + "rows": 946, + "digest": "6f90b741fcc6dbfd4e297bfd56a1c179" + }, + "recursive": { + "rows": 0, + "digest": "4f5ddea76d29cfcfd8c595f14e31f21b" + }, + "sha3": { + "rows": 14919, + "digest": "cfc49005fe4a108be200834e064354c3" + } + }, + "verificationKey": { + "data": "AQExT2L4AWXqY4ibNvWbfrA+7V/BnY/QWtvoN8gCy/JoK2hoLkbLhY0cXy/P3pMEBR6AEWp46cxsmmIv50za/70dltTzUPlyKitiM8XhDU4hqin8dql4vS37u8JNYatwiSVu5MbTYpJX2hvn6e8XADxGucO6BW4mLGaLS4RHD1GjO28HNv5fyab1Ndl8JofA+8AFbpG1Jlxm6pgfamXQHUkkf+LISFpx6biNeSIiss3EOiyebcqIPwd+QPz49DwhvA4eC8RvtGfQ/5JQlvIYwQKQvJD2sYuRbaRavoOfywZ0OTT9xting/5hzbGwFoHPBRq4vZTGmw/9NcxbOJnESdEQ4jyG5U9xcUoS/ZihRPrsNCPxQDjk7nLo3fO4EIPs1QG9uz5YhkkHWtDGkXdkaC4mIVqUoHNZTHiUPEH2Cy7tB8shddphO2gDhsTOYKDml+ASLqpDapu9vSAEcyNAZW4TNv53WS2Kx8tGGwsUXss0tnjtkc2kGXL+k/KVdfgFHy7gM2RKnP+xm9nv8xEIG6IzEdbsbrATqHz8p3o/9a1nIRkl2z63JrDxpxsRvLHlQBNvnPHiANflIWomFuW6VJ8VAG6SFM3CUd5HuvlFYdXf5WHzTEbnl+CVhM7u6WSePF0Pv9S5W21u2BDNQValTlfJUQnC6s951bw+ubdWpsgfwgUnHKUd9lkjhfcZkWve+azZMtsaqw+M0Yg0A8XxxwSoBeLkpbAV+O7pJ81BCJt994nrOX8sZFnUhkrADcIqsPkLKPzWwrPNqx5fNeszqennJ0ddaXhYxeJOnl7WZLOeZhImZDYJEteIkq/ApsKCl/AweQ6YpzfGQfcqDz/371mfOaJnHQfkJ1xNZk5AWvZPR5ac4YfHyrHcLAq9YBXDkLsQ3GIGLeVGU+YwBsUcKfzQ2REmsZGeQNU7+ynJkePgwgm9gOSUL3TuW4M5f0cpSuFFM+OiR2wMyH7/PqZf0stkJDuG2OTF9Ml1njepMB9u2cjM830ZcRja8wnITg1ZUdMyni0g6iw7pkCR17J69YmCRcuW9tqcAsu+bKi1pYmpdwwQMCQlPzrlHMoa7IieBUNMM2kEeNdq7DDWJR5VWq/5JeO5GXaUFV4d44+gFuLYPTxW4gg3R/MFP+JND1o2vQYUYbs9rTgrQo+dON4Ck58ffThRoMNnk7LxB1y5jkfe0hjb2zE/HXKChdo+fIljdH+QsMUA2UhUAFf3vOyqeeQoF4NwuVJZY3nY5ewiK/yOefv6mR0WVk4Wzu00vFzThokNAOGxnLXLv2jZwWnfFuh3hKLIZ/pLyOKMGsu0ggAWFilVsW8wBcUefvdx/jZThlwm5EbJLoLuGOQ6albp6l1fP16pT3BPS6XeeZmv5lEPtb1M9TH3jzwY+AJGcAsx3eQo6/ffLrE3LKW+04nx+fJOsF4vhij6xPcaetjoKIs+mgsLmaDTI0KInfGdlZJrAEfQitUkMQDEPbKqvs/3s46jKbbgmb/+vr1paXwUX8gjIHpI5AtDeQT/VjM5Sc7JzXQJWqBCHtTyllIo5/mqEbit19RHtgMU77IeB0cgr6Rhzz9oKFEvzsI86dMnlmd12WQeJWtEs7OuxXdRvUe7H/tFC0DA2bah4diaXKtEiAxYsn8tMVVNJyWQmpiro6joJdEJoX6ddthN9PBzoFqzxHhN5F7gXELvBhvCnYtSxifcTDizGzLDw+M9VjL7Timz9HgTWpraWZVvTKDnfU2PHD6MO/AglJ3D4G7/ecSg563bm4I8Sx9JOcDvGQv4znXQLfQDORa5S2nkftBYO/f+26UvwkPu2YVIzroJYLVLDISYRRWsE6XRdo/Zqu5YWlHKhDm0+GvjCUdg3Z6mUlReHckBDwA8D1JNTIovdwo7iK2R+hjqGEsH0UwypDT5EBcco3EPMaJYlAWUU6n3eFkdSn7YHF5lTvT6JaD2SJVRlmauf60o6xPH1+BaAcwsMtWZe3OYi6zdn+MYPgI2RPHI5xog1xcFPfJOzu4j+88oyO9YQPIGPKqqboXVWXdhCzECkAyZIuc6h/nBrc+C+qtC2WX+Ex6q5dUIaGk5OCGIS+xE2boaMtPpUhjrRUWQ8eiFZd4fCPcRodjVejJtVHzx1cS3nh8brMJGX+BVKdnoHYkypoPuo2Cw+UZaQbqniRTedIptGJThxgkBm8MuGODHsz0N7DCC02fUK+F1BPTNdDVwhpQ+wWtS1aiyE7KTGkbm7WxvdmQqfHV6lQ2o0XMvwQHihTtmH/m13hSG+UoAbZzhNYtQ+OnRi+c7C29KfVY6JM24BonfyRlpVfcTiOG1KfYs3ZQ+ZjG2bRsdLVI4f0qu8Nk4dn5E41NzXD+Fd1Kuv35E5qN3D7YZ0T3u+1J0y7pGbh0=", + "hash": "4253516606287263486819893080460308221292666769185505747011491890033545849409" + } + } +} \ No newline at end of file diff --git a/src/examples/vk_regression.ts b/tests/vk-regression/vk-regression.ts similarity index 62% rename from src/examples/vk_regression.ts rename to tests/vk-regression/vk-regression.ts index 6dbcd31373..9af98395a9 100644 --- a/src/examples/vk_regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -1,32 +1,48 @@ import fs from 'fs'; -import { Voting_ } from './zkapps/voting/voting.js'; -import { Membership_ } from './zkapps/voting/membership.js'; -import { HelloWorld } from './zkapps/hello_world/hello_world.js'; -import { TokenContract, createDex } from './zkapps/dex/dex.js'; -import { GroupCS } from './primitive_constraint_system.js'; +import { Voting_ } from '../../src/examples/zkapps/voting/voting.js'; +import { Membership_ } from '../../src/examples/zkapps/voting/membership.js'; +import { HelloWorld } from '../../src/examples/zkapps/hello-world/hello-world.js'; +import { TokenContract, createDex } from '../../src/examples/zkapps/dex/dex.js'; +import { + ecdsa, + keccakAndEcdsa, +} from '../../src/examples/crypto/ecdsa/ecdsa.js'; +import { SHA256Program } from '../../src/examples/crypto/sha256/sha256.js'; +import { + GroupCS, + BitwiseCS, + HashCS, + BasicCS, +} from './plain-constraint-system.js'; +import { diverse } from './diverse-zk-program.js'; // toggle this for quick iteration when debugging vk regressions const skipVerificationKeys = false; -// usage ./run ./src/examples/vk_regression.ts --bundle --dump ./src/examples/regression_test.json +// toggle to override caches +const forceRecompile = false; + +// usage ./run ./tests/vk-regression/vk-regression.ts --bundle --dump ./tests/vk-regression/vk-regression.json let dump = process.argv[4] === '--dump'; let jsonPath = process.argv[dump ? 5 : 4]; type MinimumConstraintSystem = { - analyzeMethods(): Record< - string, - { - rows: number; - digest: string; - } + analyzeMethods(): Promise< + Record< + string, + { + rows: number; + digest: string; + } + > >; - compile(): Promise<{ + compile(options?: { forceRecompile?: boolean }): Promise<{ verificationKey: { hash: { toString(): string }; data: string; }; }>; - digest(): string; + digest(): Promise; name: string; }; @@ -37,9 +53,16 @@ const ConstraintSystems: MinimumConstraintSystem[] = [ TokenContract, createDex().Dex, GroupCS, + BitwiseCS, + HashCS, + BasicCS, + ecdsa, + keccakAndEcdsa, + SHA256Program, + diverse, ]; -let filePath = jsonPath ? jsonPath : './src/examples/regression_test.json'; +let filePath = jsonPath ? jsonPath : './tests/vk-regression/vk-regression.json'; let RegressionJson: { [contractName: string]: { digest: string; @@ -56,7 +79,7 @@ try { } catch (error) { if (!dump) { throw Error( - `The requested file ${filePath} does not yet exist, try dumping the verification keys first. ./run ./src/examples/vk_regression.ts [--bundle] --dump ` + `The requested file ${filePath} does not yet exist, try dumping the verification keys first. npm run dump-vks` ); } } @@ -74,9 +97,9 @@ async function checkVk(contracts: typeof ConstraintSystems) { let { verificationKey: { data, hash }, - } = await c.compile(); + } = await c.compile({ forceRecompile }); - let methodData = c.analyzeMethods(); + let methodData = await c.analyzeMethods(); for (const methodKey in methodData) { let actualMethod = methodData[methodKey]; @@ -86,20 +109,14 @@ async function checkVk(contracts: typeof ConstraintSystems) { errorStack += `\n\nMethod digest mismatch for ${c.name}.${methodKey}() Actual ${JSON.stringify( - { - digest: actualMethod.digest, - rows: actualMethod.rows, - }, + { digest: actualMethod.digest, rows: actualMethod.rows }, undefined, 2 )} \n Expected ${JSON.stringify( - { - digest: expectedMethod.digest, - rows: expectedMethod.rows, - }, + { digest: expectedMethod.digest, rows: expectedMethod.rows }, undefined, 2 )}`; @@ -111,14 +128,7 @@ async function checkVk(contracts: typeof ConstraintSystems) { c.name } failed, because of a verification key mismatch. Contract has - ${JSON.stringify( - { - data, - hash, - }, - undefined, - 2 - )} + ${JSON.stringify({ data, hash }, undefined, 2)} \n but expected was ${JSON.stringify(ref.verificationKey, undefined, 2)}`; @@ -133,12 +143,13 @@ but expected was async function dumpVk(contracts: typeof ConstraintSystems) { let newEntries: typeof RegressionJson = {}; for await (const c of contracts) { - let data = c.analyzeMethods(); - let digest = c.digest(); + let data = await c.analyzeMethods(); + let digest = await c.digest(); let verificationKey: | { data: string; hash: { toString(): string } } | undefined; - if (!skipVerificationKeys) ({ verificationKey } = await c.compile()); + if (!skipVerificationKeys) + ({ verificationKey } = await c.compile({ forceRecompile })); newEntries[c.name] = { digest, methods: Object.fromEntries( diff --git a/tsconfig.json b/tsconfig.json index 367f1cc41d..1884914e9c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,8 +5,8 @@ "rootDir": "./src", "outDir": "dist", "baseUrl": ".", // affects where output files end up - "target": "es2020", // goal: ship *the most modern syntax* that is supported by *all* browsers that support our Wasm - "module": "es2022", // allow top-level await + "target": "es2021", // goal: ship *the most modern syntax* that is supported by *all* browsers that support our Wasm + "module": "nodenext", // allow top-level await "moduleResolution": "nodenext", // comply with node + "type": "module" "esModuleInterop": true, // to silence jest diff --git a/tsconfig.mina-signer-web.json b/tsconfig.mina-signer-web.json index f6172c4115..a5dc691296 100644 --- a/tsconfig.mina-signer-web.json +++ b/tsconfig.mina-signer-web.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig.mina-signer.json", - "include": ["./src/mina-signer/MinaSigner.ts", "./src/**/*.web.ts"], + "include": ["./src/mina-signer/mina-signer.ts", "./src/**/*.web.ts"], + "exclude": ["./src/examples"], "compilerOptions": { "outDir": "src/mina-signer/dist/tmp" } diff --git a/tsconfig.mina-signer.json b/tsconfig.mina-signer.json index 9a6debaf3a..4c5c88a26c 100644 --- a/tsconfig.mina-signer.json +++ b/tsconfig.mina-signer.json @@ -1,5 +1,5 @@ { - "include": ["./src/mina-signer/MinaSigner.ts"], + "include": ["./src/mina-signer/mina-signer.ts"], "exclude": ["./src/**/*.unit-test.ts"], "compilerOptions": { "rootDir": "./src", diff --git a/tsconfig.node.json b/tsconfig.node.json index 7b5d2daf33..543950cc33 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -3,9 +3,8 @@ "include": [ "./src/index.ts", "./src/snarky.js", - "./src/bindings/js/wrapper.js", "./src/mina-signer/src", - "./src/mina-signer/MinaSigner.ts", + "./src/mina-signer/mina-signer.ts", "./src/js_crypto", "./src/provable" ], diff --git a/tsconfig.test.json b/tsconfig.test.json index 85717a9f8c..5d2c6e9ca3 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -2,8 +2,8 @@ "extends": "./tsconfig.json", "include": [ "./src/**/*.unit-test.ts", - "./src/snarky.js", - "./src/bindings/js/wrapper.js" + "./src/lib/**/*.ts", + "./src/snarky.js" ], "compilerOptions": { "outDir": "dist/node" diff --git a/typedoc.json b/typedoc.json index e304edcdcd..f670c1036e 100644 --- a/typedoc.json +++ b/typedoc.json @@ -1,14 +1,16 @@ { - "name": "SnarkyJS", - "plugin": ["typedoc-plugin-markdown", "typedoc-plugin-merge-modules"], - "out": "snarkyjs-reference", - "cleanOutputDir": true, - "githubPages": false, - "exclude": [ - "dist/**/*", - "src/mina-signer/**/*", - "src/examples/**/*" - ], - "entryPoints": ["src/index.ts", "src/snarky.d.ts", "src/lib/field.ts", "src/lib/group.ts", "src/lib/bool.ts"], - "entryPointStrategy": "resolve", -} \ No newline at end of file + "name": "o1js", + "plugin": ["typedoc-plugin-markdown", "typedoc-plugin-merge-modules"], + "out": "o1js-reference", + "cleanOutputDir": true, + "githubPages": false, + "exclude": ["dist/**/*", "src/mina-signer/**/*", "src/examples/**/*"], + "entryPoints": [ + "src/index.ts", + "src/snarky.d.ts", + "src/lib/field.ts", + "src/lib/group.ts", + "src/lib/bool.ts" + ], + "entryPointStrategy": "resolve" +} diff --git a/update-changelog.sh b/update-changelog.sh new file mode 100755 index 0000000000..2d16b8eeca --- /dev/null +++ b/update-changelog.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# CHANGELOG Update Script for New Releases +# +# This script automates the process of updating the CHANGELOG.md in response to new releases. +# It performs the following actions: +# +# 1. Identifies the latest version number in the CHANGELOG (following semantic versioning). +# 2. Increments the patch segment of the version number for the new release. +# 3. Retrieves the current Git commit hash and truncates it for brevity. +# 4. Captures the current date in YYYY-MM-DD format. +# 5. Updates the CHANGELOG.md file: +# - Adds a new entry for the upcoming release using the incremented version number and the current date. +# - Updates the link for the [Unreleased] section to point from the current commit to HEAD. +# +# Usage: +# It should be run in the root directory of the repository where the CHANGELOG.md is located. +# Ensure that you have the necessary permissions to commit and push changes to the repository. + +# Step 1: Capture the latest version +latest_version=$(grep -oP '\[\K[0-9]+\.[0-9]+\.[0-9]+(?=\])' CHANGELOG.md | head -1) +echo "Latest version: $latest_version" + +# Step 2: Bump the patch version +IFS='.' read -r -a version_parts <<< "$latest_version" +let version_parts[2]+=1 +new_version="${version_parts[0]}.${version_parts[1]}.${version_parts[2]}" +echo "New version: $new_version" + +# Step 3: Capture the current git commit and truncate it to the first 9 characters +current_commit=$(git rev-parse HEAD | cut -c 1-9) +echo "Current commit: $current_commit" + +# Step 4: Capture the current date in YYYY-MM-DD format +current_date=$(date +%Y-%m-%d) +echo "Current date: $current_date" + +# Step 5: Update the CHANGELOG +sed -i "s/\[Unreleased\](.*\.\.\.HEAD)/\[Unreleased\](https:\/\/github.com\/o1-labs\/o1js\/compare\/$current_commit...HEAD)\n\n## \[$new_version\](https:\/\/github.com\/o1-labs\/o1js\/compare\/1ad7333e9e...$current_commit) - $current_date/" CHANGELOG.md