diff --git a/.circleci/config.yml b/.circleci/config.yml index 9d3a6d29e1..dbdf553101 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,13 +10,13 @@ orbs: parameters: run-workflow-general: type: boolean - default: false + default: false run-workflow-npm-install: type: boolean - default: false + default: false run-workflow-protocol-coverage: type: boolean - default: false + default: false # When you need to force a rebuild of the node modules cache then bump this version node-modules-cache-version: type: integer @@ -228,7 +228,6 @@ jobs: --ignore @celo/celotool \ --ignore @celo/celocli \ --ignore @celo/env-tests \ - --ignore @celo/identity \ --ignore @celo/transactions-uri \ --ignore '@celo/wallet-*' \ run test @@ -464,28 +463,6 @@ jobs: name: Run Tests command: yarn --cwd=packages/sdk/contractkit test - # extrated to reuse the devchain generated by contractkit - identity-tests: - <<: *defaults - steps: - - attach_workspace: - at: ~/app - - run: - name: Check if the test should run - command: | - ./scripts/ci_check_if_test_should_run_v2.sh @celo/identity - # - run: - # name: Copy DevChain generated by Contractkit - # command: | - # (cp -r packages/sdk/contractkit/.tmp packages/sdk/identity/.tmp) - - run: - name: Generate DevChain - command: | - (cd packages/sdk/identity && yarn test:reset) - - run: - name: Run Tests - command: yarn --cwd=packages/sdk/identity test - # extrated to reuse the devchain generated by contractkit transactions-uri-tests: <<: *defaults @@ -805,29 +782,6 @@ jobs: name: Minor test of celocli command: ./node_modules/.bin/celocli account:new # Small test - odis-test: - <<: *defaults - # Source: https://circleci.com/docs/2.0/configuration-reference/#resource_class - resource_class: large - steps: - - attach_workspace: - at: ~/app - - run: - name: Check if the test should run - command: | - ./scripts/ci_check_if_test_should_run_v2.sh @celo/phone-number-privacy-signer,@celo/phone-number-privacy-combiner,@celo/phone-number-privacy-common - - run: - name: Run Tests for common package - command: yarn --cwd=packages/phone-number-privacy/common test:coverage - - run: - name: Run Tests for combiner - no_output_timeout: 30m - command: yarn --cwd=packages/phone-number-privacy/combiner test:coverage - - run: - name: Run Tests for signer - no_output_timeout: 30m - command: yarn --cwd=packages/phone-number-privacy/signer test:coverage - certora-test: working_directory: ~/app docker: @@ -910,7 +864,7 @@ workflows: version: 2 celo-monorepo-build: # Contitionally triggered - when: + when: or: [<< pipeline.parameters.run-workflow-general >>] jobs: - install_dependencies @@ -932,12 +886,6 @@ workflows: - wallets-test: requires: - install_dependencies - - identity-tests: - requires: - - contractkit-test - - transactions-uri-tests: - requires: - - contractkit-test - pre-protocol-test-release: requires: - lint-checks @@ -1010,12 +958,9 @@ workflows: requires: - lint-checks - contractkit-test - - odis-test: - requires: - - lint-checks npm-install-testing-cron-workflow: # Contitionally triggered - when: + when: or: [<< pipeline.parameters.run-workflow-npm-install >>] jobs: - test-typescript-npm-package-install @@ -1023,7 +968,7 @@ workflows: - test-contractkit-npm-package-install - test-celocli-npm-package-install protocol-testing-with-code-coverage-cron-workflow: - when: + when: or: [<< pipeline.parameters.run-workflow-protocol-coverage >>] jobs: - install_dependencies diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml index 2a46de9b8e..c91182486a 100644 --- a/.github/workflows/circleci.yml +++ b/.github/workflows/circleci.yml @@ -184,7 +184,6 @@ jobs: --ignore @celo/celotool \ --ignore @celo/celocli \ --ignore @celo/env-tests \ - --ignore @celo/identity \ --ignore @celo/transactions-uri \ --ignore '@celo/wallet-*' \ run test @@ -404,76 +403,6 @@ jobs: - name: Run tests run: | yarn --cwd=packages/sdk/contractkit test - identity-tests: - name: Identity Tests - runs-on: ['self-hosted', 'monorepo-node18'] - timeout-minutes: 30 - needs: [install-dependencies, contractkit-tests] - if: | - github.base_ref == 'master' || contains(github.base_ref, 'staging') || contains(github.base_ref, 'production') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/phone-number-privacy/common') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/protocol') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/dev-utils') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/sdk') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/typescript') || - contains(needs.install-dependencies.outputs.all_modified_files, ',package.json') || - contains(needs.install-dependencies.outputs.all_modified_files, ',yarn.lock') || - false - steps: - - uses: actions/cache/restore@v3 - id: cache_git - with: - path: .git - key: git-${{ github.ref }} - - uses: actions/checkout@v3 - - name: Sync workspace - uses: ./.github/actions/sync-workspace - with: - package-json-checksum: ${{ needs.install-dependencies.outputs.package-json-checksum }} - - name: Generate DevChain - run: | - cd packages/sdk/identity - yarn test:reset - - name: Run tests - run: | - yarn --cwd=packages/sdk/identity test - transactions-uri-tests: - name: Transaction URI Tests - runs-on: ['self-hosted', 'monorepo-node18'] - timeout-minutes: 30 - needs: [install-dependencies, contractkit-tests] - if: | - github.base_ref == 'master' || contains(github.base_ref, 'staging') || contains(github.base_ref, 'production') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/protocol') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/dev-utils') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/sdk') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/typescript') || - contains(needs.install-dependencies.outputs.all_modified_files, ',package.json') || - contains(needs.install-dependencies.outputs.all_modified_files, ',yarn.lock') || - false - steps: - - uses: actions/cache/restore@v3 - id: cache_git - with: - path: .git - key: git-${{ github.ref }} - - uses: actions/checkout@v3 - - name: Sync workspace - uses: ./.github/actions/sync-workspace - with: - package-json-checksum: ${{ needs.install-dependencies.outputs.package-json-checksum }} - - name: Get changed files - id: changed-files - uses: tj-actions/changed-files@v37 - with: - fetch_depth: '150' - - name: Generate DevChain - run: | - cd packages/sdk/identity - yarn test:reset - - name: Run tests - run: | - yarn --cwd=packages/sdk/identity test cli-tests: name: CeloCli Tests runs-on: ['self-hosted', 'monorepo-node18'] @@ -482,7 +411,6 @@ jobs: if: | github.base_ref == 'master' || contains(github.base_ref, 'staging') || contains(github.base_ref, 'production') || contains(needs.install-dependencies.outputs.all_modified_files, 'packages/cli') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/phone-number-privacy/common') || contains(needs.install-dependencies.outputs.all_modified_files, 'packages/protocol') || contains(needs.install-dependencies.outputs.all_modified_files, 'packages/dev-utils') || contains(needs.install-dependencies.outputs.all_modified_files, 'packages/sdk') || @@ -627,7 +555,6 @@ jobs: if: | github.base_ref == 'master' || contains(github.base_ref, 'staging') || contains(github.base_ref, 'production') || contains(needs.install-dependencies.outputs.all_modified_files, 'packages/celotool') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/phone-number-privacy/common') || contains(needs.install-dependencies.outputs.all_modified_files, 'packages/protocol') || contains(needs.install-dependencies.outputs.all_modified_files, 'packages/dev-utils') || contains(needs.install-dependencies.outputs.all_modified_files, 'packages/sdk') || @@ -716,40 +643,6 @@ jobs: command: | ${{ matrix.command }} - odis-test: - name: ODIS test - runs-on: ['self-hosted', 'monorepo-node18'] - timeout-minutes: 30 - needs: [install-dependencies, lint-checks] - if: | - github.base_ref == 'master' || contains(github.base_ref, 'staging') || contains(github.base_ref, 'production') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/phone-number-privacy') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/protocol') || - contains(needs.install-dependencies.outputs.all_modified_files, 'packages/sdk') || - contains(needs.install-dependencies.outputs.all_modified_files, ',package.json') || - contains(needs.install-dependencies.outputs.all_modified_files, ',yarn.lock') || - false - steps: - - uses: actions/cache/restore@v3 - id: cache_git - with: - path: .git - key: git-${{ github.ref }} - - uses: actions/checkout@v3 - - name: Sync workspace - uses: ./.github/actions/sync-workspace - with: - package-json-checksum: ${{ needs.install-dependencies.outputs.package-json-checksum }} - - name: Run Tests for common package - run: | - yarn --cwd=packages/phone-number-privacy/common test:coverage - - name: Run Tests for combiner - run: | - yarn --cwd=packages/phone-number-privacy/combiner test:coverage - - name: Run Tests for signer - run: | - yarn --cwd=packages/phone-number-privacy/signer test:coverage - # NOTE: This has not been fully tested as we don't have a license for certora certora-test: name: Certora test ${{ matrix.name }} diff --git a/.gitignore b/.gitignore index 27019af91b..c81a7bffab 100644 --- a/.gitignore +++ b/.gitignore @@ -101,6 +101,9 @@ packages/faucet/* packages/moonpay-auth/* packages/notification-service/* packages/react-components/* +packages/phone-number-privacy/* +packages/sdk/identity/* +packages/sdk/encrypted-backup/* # temp json file for deploy-sdks script scripts/failedSDKs.json diff --git a/dependency-graph.json b/dependency-graph.json index 09cb3ad812..368209a15c 100644 --- a/dependency-graph.json +++ b/dependency-graph.json @@ -10,7 +10,6 @@ "@celo/env-tests", "@celo/explorer", "@celo/governance", - "@celo/identity", "@celo/network-utils", "@celo/protocol", "@celo/utils" @@ -26,7 +25,6 @@ "@celo/dev-utils", "@celo/explorer", "@celo/governance", - "@celo/identity", "@celo/phone-utils", "@celo/utils", "@celo/wallet-hsm-azure", @@ -45,7 +43,6 @@ "@celo/connect", "@celo/contractkit", "@celo/cryptographic-utils", - "@celo/identity", "@celo/phone-utils", "@celo/utils" ] @@ -74,50 +71,6 @@ "location": "packages/typescript", "dependencies": [] }, - "@celo/phone-number-privacy-combiner": { - "location": "packages/phone-number-privacy/combiner", - "dependencies": [ - "@celo/contractkit", - "@celo/encrypted-backup", - "@celo/identity", - "@celo/phone-number-privacy-common", - "@celo/phone-utils", - "@celo/utils" - ] - }, - "@celo/phone-number-privacy-common": { - "location": "packages/phone-number-privacy/common", - "dependencies": [ - "@celo/base", - "@celo/contractkit", - "@celo/phone-utils", - "@celo/utils", - "@celo/wallet-local" - ] - }, - "@celo/phone-number-privacy-monitor": { - "location": "packages/phone-number-privacy/monitor", - "dependencies": [ - "@celo/base", - "@celo/contractkit", - "@celo/cryptographic-utils", - "@celo/encrypted-backup", - "@celo/identity", - "@celo/phone-number-privacy-common", - "@celo/utils", - "@celo/wallet-local" - ] - }, - "@celo/phone-number-privacy-signer": { - "location": "packages/phone-number-privacy/signer", - "dependencies": [ - "@celo/base", - "@celo/contractkit", - "@celo/phone-number-privacy-common", - "@celo/utils", - "@celo/wallet-hsm-azure" - ] - }, "@celo/base": { "location": "packages/sdk/base", "dependencies": [ @@ -151,18 +104,6 @@ "@celo/utils" ] }, - "@celo/encrypted-backup": { - "location": "packages/sdk/encrypted-backup", - "dependencies": [ - "@celo/base", - "@celo/connect", - "@celo/dev-utils", - "@celo/identity", - "@celo/phone-number-privacy-common", - "@celo/utils", - "@celo/wallet-local" - ] - }, "@celo/explorer": { "location": "packages/sdk/explorer", "dependencies": [ @@ -182,17 +123,6 @@ "@celo/utils" ] }, - "@celo/identity": { - "location": "packages/sdk/identity", - "dependencies": [ - "@celo/base", - "@celo/contractkit", - "@celo/dev-utils", - "@celo/phone-number-privacy-common", - "@celo/utils", - "@celo/wallet-local" - ] - }, "@celo/keystores": { "location": "packages/sdk/keystores", "dependencies": [ diff --git a/dockerfiles/celotool/Dockerfile b/dockerfiles/celotool/Dockerfile index e9f23bb552..ad4b0c5a24 100644 --- a/dockerfiles/celotool/Dockerfile +++ b/dockerfiles/celotool/Dockerfile @@ -20,7 +20,6 @@ COPY lerna.json package.json yarn.lock ./ COPY packages/celotool/package.json packages/celotool/ COPY packages/dev-utils/package.json packages/dev-utils/ COPY packages/env-tests/package.json packages/env-tests/package.json -COPY packages/phone-number-privacy/common/package.json packages/phone-number-privacy/common/package.json COPY packages/protocol/package.json packages/protocol/ COPY packages/sdk/base/package.json packages/sdk/base/ COPY packages/sdk/connect/package.json packages/sdk/connect/ @@ -28,7 +27,6 @@ COPY packages/sdk/contractkit/package.json packages/sdk/contractkit/ COPY packages/sdk/cryptographic-utils/package.json packages/sdk/cryptographic-utils/ COPY packages/sdk/explorer/package.json packages/sdk/explorer/ COPY packages/sdk/governance/package.json packages/sdk/governance/ -COPY packages/sdk/identity/package.json packages/sdk/identity/ COPY packages/sdk/network-utils/package.json packages/sdk/network-utils/ COPY packages/sdk/utils/package.json packages/sdk/utils/ COPY packages/sdk/wallets/wallet-base/package.json packages/sdk/wallets/wallet-base/ @@ -38,8 +36,8 @@ COPY patches/ patches/ COPY scripts/ scripts/ COPY packages/sdk/phone-utils/package.json packages/sdk/phone-utils/package.json # Makes build fail if it doesn't copy git, will be removed after build -COPY .git .git -COPY .gitmodules .gitmodules +COPY .git .git +COPY .gitmodules .gitmodules RUN yarn install --network-timeout 100000 --frozen-lockfile && yarn cache clean @@ -47,7 +45,6 @@ COPY packages/celotool packages/celotool/ COPY packages/dev-utils packages/dev-utils/ COPY packages/env-tests packages/env-tests COPY packages/helm-charts packages/helm-charts -COPY packages/phone-number-privacy/common packages/phone-number-privacy/common COPY packages/protocol packages/protocol/ COPY packages/sdk/base packages/sdk/base/ COPY packages/sdk/connect packages/sdk/connect/ @@ -55,7 +52,6 @@ COPY packages/sdk/contractkit packages/sdk/contractkit/ COPY packages/sdk/cryptographic-utils packages/sdk/cryptographic-utils/ COPY packages/sdk/explorer packages/sdk/explorer/ COPY packages/sdk/governance packages/sdk/governance/ -COPY packages/sdk/identity packages/sdk/identity/ COPY packages/sdk/network-utils packages/sdk/network-utils/ COPY packages/sdk/utils packages/sdk/utils/ COPY packages/sdk/wallets/wallet-base packages/sdk/wallets/wallet-base/ diff --git a/dockerfiles/phone-number-privacy/Dockerfile b/dockerfiles/phone-number-privacy/Dockerfile deleted file mode 100644 index 65cf6acef1..0000000000 --- a/dockerfiles/phone-number-privacy/Dockerfile +++ /dev/null @@ -1,58 +0,0 @@ -##### Gathering dependencies -FROM scratch AS packages - -# Copy phone-number-privacy package and its dependency closure. -# Assemble all dependencies into the packages folder so the second stage can select whether to -# include all packages, or just the phone-number-privacy packages. -WORKDIR /celo-phone-number-privacy/ -COPY packages/typescript packages/typescript -COPY packages/dev-utils packages/dev-utils -COPY packages/phone-number-privacy/signer packages/phone-number-privacy/signer -COPY packages/phone-number-privacy/common packages/phone-number-privacy/common -COPY packages/protocol packages/protocol -COPY packages/sdk/base packages/sdk/base -COPY packages/sdk/connect packages/sdk/connect -COPY packages/sdk/contractkit packages/sdk/contractkit -COPY packages/sdk/cryptographic-utils packages/sdk/cryptographic-utils -COPY packages/sdk/utils packages/sdk/utils -COPY packages/sdk/phone-utils packages/sdk/phone-utils -COPY packages/sdk/wallets/wallet-base packages/sdk/wallets/wallet-base -COPY packages/sdk/wallets/wallet-hsm packages/sdk/wallets/wallet-hsm -COPY packages/sdk/wallets/wallet-hsm-azure packages/sdk/wallets/wallet-hsm-azure -COPY packages/sdk/wallets/wallet-local packages/sdk/wallets/wallet-local -COPY packages/sdk/wallets/wallet-remote packages/sdk/wallets/wallet-remote - -##### Main stage -FROM node:18 -LABEL org.opencontainers.image.authors="devops@clabs.co" - -WORKDIR /celo-phone-number-privacy/ - -# Copy monorepo settings -COPY lerna.json package.json yarn.lock ./ -COPY scripts/ scripts/ -COPY patches/ patches/ - -# Makes build fail if it doesn't copy git, will be removed after build -COPY .git .git -COPY .gitmodules .gitmodules - -# Setting ONLY_PUBLISHED_DEPENDENCIES to true or any non-empty string results in only the -# phone-number-privacy package being copied into the image, and therefore it will only build using -# published dependencies. Setting ONLY_PUBLISHED_DEPENDENCIES to "" will copy in all dependecies. -ARG ONLY_PUBLISHED_DEPENDENCIES="" -ARG PACKAGE_SELECTOR=${ONLY_PUBLISHED_DEPENDENCIES:+phone-number-privacy/signer} -COPY --from=packages celo-phone-number-privacy/packages/${PACKAGE_SELECTOR} packages/${PACKAGE_SELECTOR} - -# Install dependencies and build. -RUN yarn install --network-timeout 100000 --frozen-lockfile && yarn cache clean -RUN yarn build - -RUN rm -r .git -RUN rm .gitmodules - -# Setup and run the signer application. -ENV NODE_ENV production -WORKDIR /celo-phone-number-privacy/packages/phone-number-privacy/signer -EXPOSE 8080 -ENTRYPOINT ["yarn", "start:docker"] diff --git a/lerna.json b/lerna.json index 3be4bcdab0..6186a6723f 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,6 @@ "lerna": "2.10.1", "packages": [ "packages/*", - "packages/phone-number-privacy/*", "packages/sdk/*", "packages/sdk/wallets/*" ], diff --git a/packages/celotool/package.json b/packages/celotool/package.json index f99b256dd1..4d15e58f87 100644 --- a/packages/celotool/package.json +++ b/packages/celotool/package.json @@ -13,7 +13,6 @@ "@celo/env-tests": "1.0.0", "@celo/explorer": "5.0.4", "@celo/governance": "5.0.4", - "@celo/identity": "5.0.4", "@celo/network-utils": "5.0.4", "@celo/utils": "5.0.4", "@ethereumjs/util": "8.0.5", diff --git a/packages/docs/sdk/docs/README.md b/packages/docs/sdk/docs/README.md index afe4d7050a..ac2255c9c0 100644 --- a/packages/docs/sdk/docs/README.md +++ b/packages/docs/sdk/docs/README.md @@ -7,10 +7,8 @@ You can find more information about the specific packages at the following links - [Base](./base) - [Connect](./connect) - [ContractKit](./contractkit) -- [Encrypted Backup](./ecrypted-backup) - [Explorer](./explorer) - [Governance](./governance) -- [Identity](./identity) - [Keystores](./keystores) - [Network Utils](./network-utils) - [Phone Utils](./phone-utils) diff --git a/packages/docs/sdk/docs/encrypted-backup/README.md b/packages/docs/sdk/docs/encrypted-backup/README.md deleted file mode 100644 index 691631effc..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/README.md +++ /dev/null @@ -1,15 +0,0 @@ -[@celo/encrypted-backup](README.md) - -# @celo/encrypted-backup - -## Index - -### Modules - -* ["backup"](modules/_backup_.md) -* ["config"](modules/_config_.md) -* ["errors"](modules/_errors_.md) -* ["odis"](modules/_odis_.md) -* ["odis.mock"](modules/_odis_mock_.md) -* ["schema"](modules/_schema_.md) -* ["utils"](modules/_utils_.md) diff --git a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.authorizationerror.md b/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.authorizationerror.md deleted file mode 100644 index b8ac559db0..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.authorizationerror.md +++ /dev/null @@ -1,93 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](../modules/_errors_.md) › [AuthorizationError](_errors_.authorizationerror.md) - -# Class: AuthorizationError - -## Hierarchy - -* RootError‹[AUTHORIZATION_ERROR](../enums/_errors_.backuperrortypes.md#authorization_error)› - - ↳ **AuthorizationError** - -## Implements - -* BaseError‹[AUTHORIZATION_ERROR](../enums/_errors_.backuperrortypes.md#authorization_error)› - -## Index - -### Constructors - -* [constructor](_errors_.authorizationerror.md#constructor) - -### Properties - -* [error](_errors_.authorizationerror.md#optional-readonly-error) -* [errorType](_errors_.authorizationerror.md#readonly-errortype) -* [message](_errors_.authorizationerror.md#message) -* [name](_errors_.authorizationerror.md#name) -* [stack](_errors_.authorizationerror.md#optional-stack) - -## Constructors - -### constructor - -\+ **new AuthorizationError**(`error?`: Error): *[AuthorizationError](_errors_.authorizationerror.md)* - -*Overrides void* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:20](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L20)* - -**Parameters:** - -Name | Type | ------- | ------ | -`error?` | Error | - -**Returns:** *[AuthorizationError](_errors_.authorizationerror.md)* - -## Properties - -### `Optional` `Readonly` error - -• **error**? : *Error* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:21](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L21)* - -___ - -### `Readonly` errorType - -• **errorType**: *[AUTHORIZATION_ERROR](../enums/_errors_.backuperrortypes.md#authorization_error)* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[errorType](_errors_.authorizationerror.md#readonly-errortype)* - -Defined in packages/sdk/base/lib/result.d.ts:19 - -___ - -### message - -• **message**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[message](_errors_.authorizationerror.md#message)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:974 - -___ - -### name - -• **name**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[name](_errors_.authorizationerror.md#name)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:973 - -___ - -### `Optional` stack - -• **stack**? : *undefined | string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[stack](_errors_.authorizationerror.md#optional-stack)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:975 diff --git a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.decodeerror.md b/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.decodeerror.md deleted file mode 100644 index 35b31b654d..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.decodeerror.md +++ /dev/null @@ -1,93 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](../modules/_errors_.md) › [DecodeError](_errors_.decodeerror.md) - -# Class: DecodeError - -## Hierarchy - -* RootError‹[DECODE_ERROR](../enums/_errors_.backuperrortypes.md#decode_error)› - - ↳ **DecodeError** - -## Implements - -* BaseError‹[DECODE_ERROR](../enums/_errors_.backuperrortypes.md#decode_error)› - -## Index - -### Constructors - -* [constructor](_errors_.decodeerror.md#constructor) - -### Properties - -* [error](_errors_.decodeerror.md#optional-readonly-error) -* [errorType](_errors_.decodeerror.md#readonly-errortype) -* [message](_errors_.decodeerror.md#message) -* [name](_errors_.decodeerror.md#name) -* [stack](_errors_.decodeerror.md#optional-stack) - -## Constructors - -### constructor - -\+ **new DecodeError**(`error?`: Error): *[DecodeError](_errors_.decodeerror.md)* - -*Overrides void* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:26](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L26)* - -**Parameters:** - -Name | Type | ------- | ------ | -`error?` | Error | - -**Returns:** *[DecodeError](_errors_.decodeerror.md)* - -## Properties - -### `Optional` `Readonly` error - -• **error**? : *Error* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:27](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L27)* - -___ - -### `Readonly` errorType - -• **errorType**: *[DECODE_ERROR](../enums/_errors_.backuperrortypes.md#decode_error)* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[errorType](_errors_.authorizationerror.md#readonly-errortype)* - -Defined in packages/sdk/base/lib/result.d.ts:19 - -___ - -### message - -• **message**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[message](_errors_.authorizationerror.md#message)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:974 - -___ - -### name - -• **name**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[name](_errors_.authorizationerror.md#name)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:973 - -___ - -### `Optional` stack - -• **stack**? : *undefined | string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[stack](_errors_.authorizationerror.md#optional-stack)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:975 diff --git a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.decryptionerror.md b/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.decryptionerror.md deleted file mode 100644 index 7d9669c5d9..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.decryptionerror.md +++ /dev/null @@ -1,93 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](../modules/_errors_.md) › [DecryptionError](_errors_.decryptionerror.md) - -# Class: DecryptionError - -## Hierarchy - -* RootError‹[DECRYPTION_ERROR](../enums/_errors_.backuperrortypes.md#decryption_error)› - - ↳ **DecryptionError** - -## Implements - -* BaseError‹[DECRYPTION_ERROR](../enums/_errors_.backuperrortypes.md#decryption_error)› - -## Index - -### Constructors - -* [constructor](_errors_.decryptionerror.md#constructor) - -### Properties - -* [error](_errors_.decryptionerror.md#optional-readonly-error) -* [errorType](_errors_.decryptionerror.md#readonly-errortype) -* [message](_errors_.decryptionerror.md#message) -* [name](_errors_.decryptionerror.md#name) -* [stack](_errors_.decryptionerror.md#optional-stack) - -## Constructors - -### constructor - -\+ **new DecryptionError**(`error?`: Error): *[DecryptionError](_errors_.decryptionerror.md)* - -*Overrides void* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:32](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L32)* - -**Parameters:** - -Name | Type | ------- | ------ | -`error?` | Error | - -**Returns:** *[DecryptionError](_errors_.decryptionerror.md)* - -## Properties - -### `Optional` `Readonly` error - -• **error**? : *Error* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:33](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L33)* - -___ - -### `Readonly` errorType - -• **errorType**: *[DECRYPTION_ERROR](../enums/_errors_.backuperrortypes.md#decryption_error)* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[errorType](_errors_.authorizationerror.md#readonly-errortype)* - -Defined in packages/sdk/base/lib/result.d.ts:19 - -___ - -### message - -• **message**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[message](_errors_.authorizationerror.md#message)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:974 - -___ - -### name - -• **name**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[name](_errors_.authorizationerror.md#name)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:973 - -___ - -### `Optional` stack - -• **stack**? : *undefined | string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[stack](_errors_.authorizationerror.md#optional-stack)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:975 diff --git a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.encryptionerror.md b/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.encryptionerror.md deleted file mode 100644 index 4a5466a7db..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.encryptionerror.md +++ /dev/null @@ -1,93 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](../modules/_errors_.md) › [EncryptionError](_errors_.encryptionerror.md) - -# Class: EncryptionError - -## Hierarchy - -* RootError‹[ENCRYPTION_ERROR](../enums/_errors_.backuperrortypes.md#encryption_error)› - - ↳ **EncryptionError** - -## Implements - -* BaseError‹[ENCRYPTION_ERROR](../enums/_errors_.backuperrortypes.md#encryption_error)› - -## Index - -### Constructors - -* [constructor](_errors_.encryptionerror.md#constructor) - -### Properties - -* [error](_errors_.encryptionerror.md#optional-readonly-error) -* [errorType](_errors_.encryptionerror.md#readonly-errortype) -* [message](_errors_.encryptionerror.md#message) -* [name](_errors_.encryptionerror.md#name) -* [stack](_errors_.encryptionerror.md#optional-stack) - -## Constructors - -### constructor - -\+ **new EncryptionError**(`error?`: Error): *[EncryptionError](_errors_.encryptionerror.md)* - -*Overrides void* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:38](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L38)* - -**Parameters:** - -Name | Type | ------- | ------ | -`error?` | Error | - -**Returns:** *[EncryptionError](_errors_.encryptionerror.md)* - -## Properties - -### `Optional` `Readonly` error - -• **error**? : *Error* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:39](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L39)* - -___ - -### `Readonly` errorType - -• **errorType**: *[ENCRYPTION_ERROR](../enums/_errors_.backuperrortypes.md#encryption_error)* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[errorType](_errors_.authorizationerror.md#readonly-errortype)* - -Defined in packages/sdk/base/lib/result.d.ts:19 - -___ - -### message - -• **message**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[message](_errors_.authorizationerror.md#message)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:974 - -___ - -### name - -• **name**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[name](_errors_.authorizationerror.md#name)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:973 - -___ - -### `Optional` stack - -• **stack**? : *undefined | string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[stack](_errors_.authorizationerror.md#optional-stack)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:975 diff --git a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.fetcherror.md b/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.fetcherror.md deleted file mode 100644 index eaae1fc8ed..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.fetcherror.md +++ /dev/null @@ -1,93 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](../modules/_errors_.md) › [FetchError](_errors_.fetcherror.md) - -# Class: FetchError - -## Hierarchy - -* RootError‹[FETCH_ERROR](../enums/_errors_.backuperrortypes.md#fetch_error)› - - ↳ **FetchError** - -## Implements - -* BaseError‹[FETCH_ERROR](../enums/_errors_.backuperrortypes.md#fetch_error)› - -## Index - -### Constructors - -* [constructor](_errors_.fetcherror.md#constructor) - -### Properties - -* [error](_errors_.fetcherror.md#optional-readonly-error) -* [errorType](_errors_.fetcherror.md#readonly-errortype) -* [message](_errors_.fetcherror.md#message) -* [name](_errors_.fetcherror.md#name) -* [stack](_errors_.fetcherror.md#optional-stack) - -## Constructors - -### constructor - -\+ **new FetchError**(`error?`: Error): *[FetchError](_errors_.fetcherror.md)* - -*Overrides void* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:44](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L44)* - -**Parameters:** - -Name | Type | ------- | ------ | -`error?` | Error | - -**Returns:** *[FetchError](_errors_.fetcherror.md)* - -## Properties - -### `Optional` `Readonly` error - -• **error**? : *Error* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:45](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L45)* - -___ - -### `Readonly` errorType - -• **errorType**: *[FETCH_ERROR](../enums/_errors_.backuperrortypes.md#fetch_error)* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[errorType](_errors_.authorizationerror.md#readonly-errortype)* - -Defined in packages/sdk/base/lib/result.d.ts:19 - -___ - -### message - -• **message**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[message](_errors_.authorizationerror.md#message)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:974 - -___ - -### name - -• **name**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[name](_errors_.authorizationerror.md#name)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:973 - -___ - -### `Optional` stack - -• **stack**? : *undefined | string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[stack](_errors_.authorizationerror.md#optional-stack)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:975 diff --git a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.invalidbackuperror.md b/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.invalidbackuperror.md deleted file mode 100644 index c577e1558b..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.invalidbackuperror.md +++ /dev/null @@ -1,93 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](../modules/_errors_.md) › [InvalidBackupError](_errors_.invalidbackuperror.md) - -# Class: InvalidBackupError - -## Hierarchy - -* RootError‹[INVALID_BACKUP_ERROR](../enums/_errors_.backuperrortypes.md#invalid_backup_error)› - - ↳ **InvalidBackupError** - -## Implements - -* BaseError‹[INVALID_BACKUP_ERROR](../enums/_errors_.backuperrortypes.md#invalid_backup_error)› - -## Index - -### Constructors - -* [constructor](_errors_.invalidbackuperror.md#constructor) - -### Properties - -* [error](_errors_.invalidbackuperror.md#optional-readonly-error) -* [errorType](_errors_.invalidbackuperror.md#readonly-errortype) -* [message](_errors_.invalidbackuperror.md#message) -* [name](_errors_.invalidbackuperror.md#name) -* [stack](_errors_.invalidbackuperror.md#optional-stack) - -## Constructors - -### constructor - -\+ **new InvalidBackupError**(`error?`: Error): *[InvalidBackupError](_errors_.invalidbackuperror.md)* - -*Overrides void* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:50](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L50)* - -**Parameters:** - -Name | Type | ------- | ------ | -`error?` | Error | - -**Returns:** *[InvalidBackupError](_errors_.invalidbackuperror.md)* - -## Properties - -### `Optional` `Readonly` error - -• **error**? : *Error* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:51](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L51)* - -___ - -### `Readonly` errorType - -• **errorType**: *[INVALID_BACKUP_ERROR](../enums/_errors_.backuperrortypes.md#invalid_backup_error)* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[errorType](_errors_.authorizationerror.md#readonly-errortype)* - -Defined in packages/sdk/base/lib/result.d.ts:19 - -___ - -### message - -• **message**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[message](_errors_.authorizationerror.md#message)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:974 - -___ - -### name - -• **name**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[name](_errors_.authorizationerror.md#name)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:973 - -___ - -### `Optional` stack - -• **stack**? : *undefined | string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[stack](_errors_.authorizationerror.md#optional-stack)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:975 diff --git a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.odisratelimitingerror.md b/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.odisratelimitingerror.md deleted file mode 100644 index faa049f208..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.odisratelimitingerror.md +++ /dev/null @@ -1,103 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](../modules/_errors_.md) › [OdisRateLimitingError](_errors_.odisratelimitingerror.md) - -# Class: OdisRateLimitingError - -## Hierarchy - -* RootError‹[ODIS_RATE_LIMITING_ERROR](../enums/_errors_.backuperrortypes.md#odis_rate_limiting_error)› - - ↳ **OdisRateLimitingError** - -## Implements - -* BaseError‹[ODIS_RATE_LIMITING_ERROR](../enums/_errors_.backuperrortypes.md#odis_rate_limiting_error)› - -## Index - -### Constructors - -* [constructor](_errors_.odisratelimitingerror.md#constructor) - -### Properties - -* [error](_errors_.odisratelimitingerror.md#optional-readonly-error) -* [errorType](_errors_.odisratelimitingerror.md#readonly-errortype) -* [message](_errors_.odisratelimitingerror.md#message) -* [name](_errors_.odisratelimitingerror.md#name) -* [notBefore](_errors_.odisratelimitingerror.md#optional-readonly-notbefore) -* [stack](_errors_.odisratelimitingerror.md#optional-stack) - -## Constructors - -### constructor - -\+ **new OdisRateLimitingError**(`notBefore?`: undefined | number, `error?`: Error): *[OdisRateLimitingError](_errors_.odisratelimitingerror.md)* - -*Overrides void* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:62](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L62)* - -**Parameters:** - -Name | Type | ------- | ------ | -`notBefore?` | undefined | number | -`error?` | Error | - -**Returns:** *[OdisRateLimitingError](_errors_.odisratelimitingerror.md)* - -## Properties - -### `Optional` `Readonly` error - -• **error**? : *Error* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:63](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L63)* - -___ - -### `Readonly` errorType - -• **errorType**: *[ODIS_RATE_LIMITING_ERROR](../enums/_errors_.backuperrortypes.md#odis_rate_limiting_error)* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[errorType](_errors_.authorizationerror.md#readonly-errortype)* - -Defined in packages/sdk/base/lib/result.d.ts:19 - -___ - -### message - -• **message**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[message](_errors_.authorizationerror.md#message)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:974 - -___ - -### name - -• **name**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[name](_errors_.authorizationerror.md#name)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:973 - -___ - -### `Optional` `Readonly` notBefore - -• **notBefore**? : *undefined | number* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:63](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L63)* - -___ - -### `Optional` stack - -• **stack**? : *undefined | string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[stack](_errors_.authorizationerror.md#optional-stack)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:975 diff --git a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.odisserviceerror.md b/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.odisserviceerror.md deleted file mode 100644 index eb6fb6b372..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.odisserviceerror.md +++ /dev/null @@ -1,103 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](../modules/_errors_.md) › [OdisServiceError](_errors_.odisserviceerror.md) - -# Class: OdisServiceError - -## Hierarchy - -* RootError‹[ODIS_SERVICE_ERROR](../enums/_errors_.backuperrortypes.md#odis_service_error)› - - ↳ **OdisServiceError** - -## Implements - -* BaseError‹[ODIS_SERVICE_ERROR](../enums/_errors_.backuperrortypes.md#odis_service_error)› - -## Index - -### Constructors - -* [constructor](_errors_.odisserviceerror.md#constructor) - -### Properties - -* [error](_errors_.odisserviceerror.md#optional-readonly-error) -* [errorType](_errors_.odisserviceerror.md#readonly-errortype) -* [message](_errors_.odisserviceerror.md#message) -* [name](_errors_.odisserviceerror.md#name) -* [stack](_errors_.odisserviceerror.md#optional-stack) -* [version](_errors_.odisserviceerror.md#optional-readonly-version) - -## Constructors - -### constructor - -\+ **new OdisServiceError**(`error?`: Error, `version?`: undefined | string): *[OdisServiceError](_errors_.odisserviceerror.md)* - -*Overrides void* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:56](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L56)* - -**Parameters:** - -Name | Type | ------- | ------ | -`error?` | Error | -`version?` | undefined | string | - -**Returns:** *[OdisServiceError](_errors_.odisserviceerror.md)* - -## Properties - -### `Optional` `Readonly` error - -• **error**? : *Error* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:57](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L57)* - -___ - -### `Readonly` errorType - -• **errorType**: *[ODIS_SERVICE_ERROR](../enums/_errors_.backuperrortypes.md#odis_service_error)* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[errorType](_errors_.authorizationerror.md#readonly-errortype)* - -Defined in packages/sdk/base/lib/result.d.ts:19 - -___ - -### message - -• **message**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[message](_errors_.authorizationerror.md#message)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:974 - -___ - -### name - -• **name**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[name](_errors_.authorizationerror.md#name)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:973 - -___ - -### `Optional` stack - -• **stack**? : *undefined | string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[stack](_errors_.authorizationerror.md#optional-stack)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:975 - -___ - -### `Optional` `Readonly` version - -• **version**? : *undefined | string* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:57](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L57)* diff --git a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.odisverificationerror.md b/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.odisverificationerror.md deleted file mode 100644 index 36892811c1..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.odisverificationerror.md +++ /dev/null @@ -1,93 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](../modules/_errors_.md) › [OdisVerificationError](_errors_.odisverificationerror.md) - -# Class: OdisVerificationError - -## Hierarchy - -* RootError‹[ODIS_VERIFICATION_ERROR](../enums/_errors_.backuperrortypes.md#odis_verification_error)› - - ↳ **OdisVerificationError** - -## Implements - -* BaseError‹[ODIS_VERIFICATION_ERROR](../enums/_errors_.backuperrortypes.md#odis_verification_error)› - -## Index - -### Constructors - -* [constructor](_errors_.odisverificationerror.md#constructor) - -### Properties - -* [error](_errors_.odisverificationerror.md#optional-readonly-error) -* [errorType](_errors_.odisverificationerror.md#readonly-errortype) -* [message](_errors_.odisverificationerror.md#message) -* [name](_errors_.odisverificationerror.md#name) -* [stack](_errors_.odisverificationerror.md#optional-stack) - -## Constructors - -### constructor - -\+ **new OdisVerificationError**(`error?`: Error): *[OdisVerificationError](_errors_.odisverificationerror.md)* - -*Overrides void* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:68](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L68)* - -**Parameters:** - -Name | Type | ------- | ------ | -`error?` | Error | - -**Returns:** *[OdisVerificationError](_errors_.odisverificationerror.md)* - -## Properties - -### `Optional` `Readonly` error - -• **error**? : *Error* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:69](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L69)* - -___ - -### `Readonly` errorType - -• **errorType**: *[ODIS_VERIFICATION_ERROR](../enums/_errors_.backuperrortypes.md#odis_verification_error)* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[errorType](_errors_.authorizationerror.md#readonly-errortype)* - -Defined in packages/sdk/base/lib/result.d.ts:19 - -___ - -### message - -• **message**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[message](_errors_.authorizationerror.md#message)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:974 - -___ - -### name - -• **name**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[name](_errors_.authorizationerror.md#name)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:973 - -___ - -### `Optional` stack - -• **stack**? : *undefined | string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[stack](_errors_.authorizationerror.md#optional-stack)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:975 diff --git a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.pbkdferror.md b/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.pbkdferror.md deleted file mode 100644 index b53cb6e5ae..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.pbkdferror.md +++ /dev/null @@ -1,103 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](../modules/_errors_.md) › [PbkdfError](_errors_.pbkdferror.md) - -# Class: PbkdfError - -## Hierarchy - -* RootError‹[PBKDF_ERROR](../enums/_errors_.backuperrortypes.md#pbkdf_error)› - - ↳ **PbkdfError** - -## Implements - -* BaseError‹[PBKDF_ERROR](../enums/_errors_.backuperrortypes.md#pbkdf_error)› - -## Index - -### Constructors - -* [constructor](_errors_.pbkdferror.md#constructor) - -### Properties - -* [error](_errors_.pbkdferror.md#optional-readonly-error) -* [errorType](_errors_.pbkdferror.md#readonly-errortype) -* [iterations](_errors_.pbkdferror.md#readonly-iterations) -* [message](_errors_.pbkdferror.md#message) -* [name](_errors_.pbkdferror.md#name) -* [stack](_errors_.pbkdferror.md#optional-stack) - -## Constructors - -### constructor - -\+ **new PbkdfError**(`iterations`: number, `error?`: Error): *[PbkdfError](_errors_.pbkdferror.md)* - -*Overrides void* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:74](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L74)* - -**Parameters:** - -Name | Type | ------- | ------ | -`iterations` | number | -`error?` | Error | - -**Returns:** *[PbkdfError](_errors_.pbkdferror.md)* - -## Properties - -### `Optional` `Readonly` error - -• **error**? : *Error* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:75](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L75)* - -___ - -### `Readonly` errorType - -• **errorType**: *[PBKDF_ERROR](../enums/_errors_.backuperrortypes.md#pbkdf_error)* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[errorType](_errors_.authorizationerror.md#readonly-errortype)* - -Defined in packages/sdk/base/lib/result.d.ts:19 - -___ - -### `Readonly` iterations - -• **iterations**: *number* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:75](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L75)* - -___ - -### message - -• **message**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[message](_errors_.authorizationerror.md#message)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:974 - -___ - -### name - -• **name**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[name](_errors_.authorizationerror.md#name)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:973 - -___ - -### `Optional` stack - -• **stack**? : *undefined | string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[stack](_errors_.authorizationerror.md#optional-stack)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:975 diff --git a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.scrypterror.md b/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.scrypterror.md deleted file mode 100644 index 5e73876fb5..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.scrypterror.md +++ /dev/null @@ -1,103 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](../modules/_errors_.md) › [ScryptError](_errors_.scrypterror.md) - -# Class: ScryptError - -## Hierarchy - -* RootError‹[SCRYPT_ERROR](../enums/_errors_.backuperrortypes.md#scrypt_error)› - - ↳ **ScryptError** - -## Implements - -* BaseError‹[SCRYPT_ERROR](../enums/_errors_.backuperrortypes.md#scrypt_error)› - -## Index - -### Constructors - -* [constructor](_errors_.scrypterror.md#constructor) - -### Properties - -* [error](_errors_.scrypterror.md#optional-readonly-error) -* [errorType](_errors_.scrypterror.md#readonly-errortype) -* [message](_errors_.scrypterror.md#message) -* [name](_errors_.scrypterror.md#name) -* [options](_errors_.scrypterror.md#readonly-options) -* [stack](_errors_.scrypterror.md#optional-stack) - -## Constructors - -### constructor - -\+ **new ScryptError**(`options`: [ScryptOptions](../interfaces/_utils_.scryptoptions.md), `error?`: Error): *[ScryptError](_errors_.scrypterror.md)* - -*Overrides void* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:80](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L80)* - -**Parameters:** - -Name | Type | ------- | ------ | -`options` | [ScryptOptions](../interfaces/_utils_.scryptoptions.md) | -`error?` | Error | - -**Returns:** *[ScryptError](_errors_.scrypterror.md)* - -## Properties - -### `Optional` `Readonly` error - -• **error**? : *Error* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:81](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L81)* - -___ - -### `Readonly` errorType - -• **errorType**: *[SCRYPT_ERROR](../enums/_errors_.backuperrortypes.md#scrypt_error)* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[errorType](_errors_.authorizationerror.md#readonly-errortype)* - -Defined in packages/sdk/base/lib/result.d.ts:19 - -___ - -### message - -• **message**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[message](_errors_.authorizationerror.md#message)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:974 - -___ - -### name - -• **name**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[name](_errors_.authorizationerror.md#name)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:973 - -___ - -### `Readonly` options - -• **options**: *[ScryptOptions](../interfaces/_utils_.scryptoptions.md)* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:81](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L81)* - -___ - -### `Optional` stack - -• **stack**? : *undefined | string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[stack](_errors_.authorizationerror.md#optional-stack)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:975 diff --git a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.usageerror.md b/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.usageerror.md deleted file mode 100644 index c8382732fa..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/classes/_errors_.usageerror.md +++ /dev/null @@ -1,93 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](../modules/_errors_.md) › [UsageError](_errors_.usageerror.md) - -# Class: UsageError - -## Hierarchy - -* RootError‹[USAGE_ERROR](../enums/_errors_.backuperrortypes.md#usage_error)› - - ↳ **UsageError** - -## Implements - -* BaseError‹[USAGE_ERROR](../enums/_errors_.backuperrortypes.md#usage_error)› - -## Index - -### Constructors - -* [constructor](_errors_.usageerror.md#constructor) - -### Properties - -* [error](_errors_.usageerror.md#optional-readonly-error) -* [errorType](_errors_.usageerror.md#readonly-errortype) -* [message](_errors_.usageerror.md#message) -* [name](_errors_.usageerror.md#name) -* [stack](_errors_.usageerror.md#optional-stack) - -## Constructors - -### constructor - -\+ **new UsageError**(`error?`: Error): *[UsageError](_errors_.usageerror.md)* - -*Overrides void* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:86](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L86)* - -**Parameters:** - -Name | Type | ------- | ------ | -`error?` | Error | - -**Returns:** *[UsageError](_errors_.usageerror.md)* - -## Properties - -### `Optional` `Readonly` error - -• **error**? : *Error* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:87](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L87)* - -___ - -### `Readonly` errorType - -• **errorType**: *[USAGE_ERROR](../enums/_errors_.backuperrortypes.md#usage_error)* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[errorType](_errors_.authorizationerror.md#readonly-errortype)* - -Defined in packages/sdk/base/lib/result.d.ts:19 - -___ - -### message - -• **message**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[message](_errors_.authorizationerror.md#message)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:974 - -___ - -### name - -• **name**: *string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[name](_errors_.authorizationerror.md#name)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:973 - -___ - -### `Optional` stack - -• **stack**? : *undefined | string* - -*Inherited from [AuthorizationError](_errors_.authorizationerror.md).[stack](_errors_.authorizationerror.md#optional-stack)* - -Defined in node_modules/typescript/lib/lib.es5.d.ts:975 diff --git a/packages/docs/sdk/docs/encrypted-backup/classes/_odis_mock_.mockodis.md b/packages/docs/sdk/docs/encrypted-backup/classes/_odis_mock_.mockodis.md deleted file mode 100644 index 6c5f343bdc..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/classes/_odis_mock_.mockodis.md +++ /dev/null @@ -1,137 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["odis.mock"](../modules/_odis_mock_.md) › [MockOdis](_odis_mock_.mockodis.md) - -# Class: MockOdis - -## Hierarchy - -* **MockOdis** - -## Index - -### Properties - -* [poprf](_odis_mock_.mockodis.md#readonly-poprf) -* [state](_odis_mock_.mockodis.md#readonly-state) -* [environment](_odis_mock_.mockodis.md#static-readonly-environment) - -### Methods - -* [install](_odis_mock_.mockodis.md#install) -* [installQuotaEndpoint](_odis_mock_.mockodis.md#installquotaendpoint) -* [installSignEndpoint](_odis_mock_.mockodis.md#installsignendpoint) -* [quota](_odis_mock_.mockodis.md#quota) -* [sign](_odis_mock_.mockodis.md#sign) - -## Properties - -### `Readonly` poprf - -• **poprf**: *any* = new PoprfServer(MOCK_ODIS_KEYPAIR.privateKey) - -*Defined in [packages/sdk/encrypted-backup/src/odis.mock.ts:32](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.mock.ts#L32)* - -___ - -### `Readonly` state - -• **state**: *Record‹string, SequentialDelayDomainState›* - -*Defined in [packages/sdk/encrypted-backup/src/odis.mock.ts:31](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.mock.ts#L31)* - -___ - -### `Static` `Readonly` environment - -▪ **environment**: *ServiceContext* = MOCK_ODIS_ENVIRONMENT - -*Defined in [packages/sdk/encrypted-backup/src/odis.mock.ts:29](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.mock.ts#L29)* - -## Methods - -### install - -▸ **install**(`mock`: typeof fetchMock): *void* - -*Defined in [packages/sdk/encrypted-backup/src/odis.mock.ts:171](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.mock.ts#L171)* - -**Parameters:** - -Name | Type | ------- | ------ | -`mock` | typeof fetchMock | - -**Returns:** *void* - -___ - -### installQuotaEndpoint - -▸ **installQuotaEndpoint**(`mock`: typeof fetchMock, `override?`: any): *void* - -*Defined in [packages/sdk/encrypted-backup/src/odis.mock.ts:137](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.mock.ts#L137)* - -**Parameters:** - -Name | Type | ------- | ------ | -`mock` | typeof fetchMock | -`override?` | any | - -**Returns:** *void* - -___ - -### installSignEndpoint - -▸ **installSignEndpoint**(`mock`: typeof fetchMock, `override?`: any): *void* - -*Defined in [packages/sdk/encrypted-backup/src/odis.mock.ts:154](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.mock.ts#L154)* - -**Parameters:** - -Name | Type | ------- | ------ | -`mock` | typeof fetchMock | -`override?` | any | - -**Returns:** *void* - -___ - -### quota - -▸ **quota**(`req`: DomainQuotaStatusRequest‹SequentialDelayDomain›): *object* - -*Defined in [packages/sdk/encrypted-backup/src/odis.mock.ts:34](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.mock.ts#L34)* - -**Parameters:** - -Name | Type | ------- | ------ | -`req` | DomainQuotaStatusRequest‹SequentialDelayDomain› | - -**Returns:** *object* - -* **body**: *DomainQuotaStatusResponse* - -* **status**: *number* - -___ - -### sign - -▸ **sign**(`req`: DomainRestrictedSignatureRequest‹SequentialDelayDomain›): *object* - -*Defined in [packages/sdk/encrypted-backup/src/odis.mock.ts:61](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.mock.ts#L61)* - -**Parameters:** - -Name | Type | ------- | ------ | -`req` | DomainRestrictedSignatureRequest‹SequentialDelayDomain› | - -**Returns:** *object* - -* **body**: *DomainRestrictedSignatureResponse* - -* **status**: *number* diff --git a/packages/docs/sdk/docs/encrypted-backup/enums/_config_.computationalhardeningfunction.md b/packages/docs/sdk/docs/encrypted-backup/enums/_config_.computationalhardeningfunction.md deleted file mode 100644 index a66fbef7c1..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/enums/_config_.computationalhardeningfunction.md +++ /dev/null @@ -1,26 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["config"](../modules/_config_.md) › [ComputationalHardeningFunction](_config_.computationalhardeningfunction.md) - -# Enumeration: ComputationalHardeningFunction - -## Index - -### Enumeration members - -* [PBKDF](_config_.computationalhardeningfunction.md#pbkdf) -* [SCRYPT](_config_.computationalhardeningfunction.md#scrypt) - -## Enumeration members - -### PBKDF - -• **PBKDF**: = "pbkdf2_sha256" - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:58](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L58)* - -___ - -### SCRYPT - -• **SCRYPT**: = "scrypt" - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:59](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L59)* diff --git a/packages/docs/sdk/docs/encrypted-backup/enums/_config_.environmentidentifier.md b/packages/docs/sdk/docs/encrypted-backup/enums/_config_.environmentidentifier.md deleted file mode 100644 index 691da1d14c..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/enums/_config_.environmentidentifier.md +++ /dev/null @@ -1,26 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["config"](../modules/_config_.md) › [EnvironmentIdentifier](_config_.environmentidentifier.md) - -# Enumeration: EnvironmentIdentifier - -## Index - -### Enumeration members - -* [ALFAJORES](_config_.environmentidentifier.md#alfajores) -* [MAINNET](_config_.environmentidentifier.md#mainnet) - -## Enumeration members - -### ALFAJORES - -• **ALFAJORES**: = "ALFAJORES" - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:253](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L253)* - -___ - -### MAINNET - -• **MAINNET**: = "MAINNET" - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:252](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L252)* diff --git a/packages/docs/sdk/docs/encrypted-backup/enums/_errors_.backuperrortypes.md b/packages/docs/sdk/docs/encrypted-backup/enums/_errors_.backuperrortypes.md deleted file mode 100644 index b803de0f23..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/enums/_errors_.backuperrortypes.md +++ /dev/null @@ -1,116 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](../modules/_errors_.md) › [BackupErrorTypes](_errors_.backuperrortypes.md) - -# Enumeration: BackupErrorTypes - -## Index - -### Enumeration members - -* [AUTHORIZATION_ERROR](_errors_.backuperrortypes.md#authorization_error) -* [DECODE_ERROR](_errors_.backuperrortypes.md#decode_error) -* [DECRYPTION_ERROR](_errors_.backuperrortypes.md#decryption_error) -* [ENCRYPTION_ERROR](_errors_.backuperrortypes.md#encryption_error) -* [FETCH_ERROR](_errors_.backuperrortypes.md#fetch_error) -* [INVALID_BACKUP_ERROR](_errors_.backuperrortypes.md#invalid_backup_error) -* [ODIS_RATE_LIMITING_ERROR](_errors_.backuperrortypes.md#odis_rate_limiting_error) -* [ODIS_SERVICE_ERROR](_errors_.backuperrortypes.md#odis_service_error) -* [ODIS_VERIFICATION_ERROR](_errors_.backuperrortypes.md#odis_verification_error) -* [PBKDF_ERROR](_errors_.backuperrortypes.md#pbkdf_error) -* [SCRYPT_ERROR](_errors_.backuperrortypes.md#scrypt_error) -* [USAGE_ERROR](_errors_.backuperrortypes.md#usage_error) - -## Enumeration members - -### AUTHORIZATION_ERROR - -• **AUTHORIZATION_ERROR**: = "AUTHORIZATION_ERROR" - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:6](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L6)* - -___ - -### DECODE_ERROR - -• **DECODE_ERROR**: = "DECODE_ERROR" - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:7](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L7)* - -___ - -### DECRYPTION_ERROR - -• **DECRYPTION_ERROR**: = "DECRYPTION_ERROR" - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:8](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L8)* - -___ - -### ENCRYPTION_ERROR - -• **ENCRYPTION_ERROR**: = "ENCRYPTION_ERROR" - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:9](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L9)* - -___ - -### FETCH_ERROR - -• **FETCH_ERROR**: = "FETCH_ERROR" - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:10](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L10)* - -___ - -### INVALID_BACKUP_ERROR - -• **INVALID_BACKUP_ERROR**: = "INVALID_BACKUP_ERROR" - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:11](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L11)* - -___ - -### ODIS_RATE_LIMITING_ERROR - -• **ODIS_RATE_LIMITING_ERROR**: = "ODIS_RATE_LIMITING_ERROR" - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:13](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L13)* - -___ - -### ODIS_SERVICE_ERROR - -• **ODIS_SERVICE_ERROR**: = "ODIS_SERVICE_ERROR" - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:12](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L12)* - -___ - -### ODIS_VERIFICATION_ERROR - -• **ODIS_VERIFICATION_ERROR**: = "ODIS_VERIFICATION_ERROR" - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:14](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L14)* - -___ - -### PBKDF_ERROR - -• **PBKDF_ERROR**: = "PBKDF_ERROR" - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:15](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L15)* - -___ - -### SCRYPT_ERROR - -• **SCRYPT_ERROR**: = "SCRYPT_ERROR" - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:16](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L16)* - -___ - -### USAGE_ERROR - -• **USAGE_ERROR**: = "USAGE_ERROR" - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:17](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L17)* diff --git a/packages/docs/sdk/docs/encrypted-backup/enums/_utils_.kdfinfo.md b/packages/docs/sdk/docs/encrypted-backup/enums/_utils_.kdfinfo.md deleted file mode 100644 index 7c315bea65..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/enums/_utils_.kdfinfo.md +++ /dev/null @@ -1,73 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["utils"](../modules/_utils_.md) › [KDFInfo](_utils_.kdfinfo.md) - -# Enumeration: KDFInfo - -Info strings to separate distinct usages of the key derivation function - -## Index - -### Enumeration members - -* [FINALIZE](_utils_.kdfinfo.md#finalize) -* [FUSE_KEY](_utils_.kdfinfo.md#fuse_key) -* [ODIS_AUTH_KEY](_utils_.kdfinfo.md#odis_auth_key) -* [ODIS_KEY_HARDENING](_utils_.kdfinfo.md#odis_key_hardening) -* [PASSWORD](_utils_.kdfinfo.md#password) -* [PBKDF](_utils_.kdfinfo.md#pbkdf) -* [SCRYPT](_utils_.kdfinfo.md#scrypt) - -## Enumeration members - -### FINALIZE - -• **FINALIZE**: = "Celo Backup Key Finalization" - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:21](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L21)* - -___ - -### FUSE_KEY - -• **FUSE_KEY**: = "Celo Backup Fuse Key" - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:16](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L16)* - -___ - -### ODIS_AUTH_KEY - -• **ODIS_AUTH_KEY**: = "Celo Backup ODIS Request Authorization Key" - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:17](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L17)* - -___ - -### ODIS_KEY_HARDENING - -• **ODIS_KEY_HARDENING**: = "Celo Backup ODIS Key Hardening" - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:18](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L18)* - -___ - -### PASSWORD - -• **PASSWORD**: = "Celo Backup Password and Nonce" - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:15](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L15)* - -___ - -### PBKDF - -• **PBKDF**: = "Celo Backup PBKDF Hardening" - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:19](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L19)* - -___ - -### SCRYPT - -• **SCRYPT**: = "Celo Backup scrypt Hardening" - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:20](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L20)* diff --git a/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.backup.md b/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.backup.md deleted file mode 100644 index d542d8f7fa..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.backup.md +++ /dev/null @@ -1,140 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["backup"](../modules/_backup_.md) › [Backup](_backup_.backup.md) - -# Interface: Backup - -Backup structure encoding the information needed to implement the encrypted backup protocol. - -**`remarks`** The structure below and its related functions implement the encrypted backup protocol -designed for wallet account backups. More information about the protocol can be found in the -official [Celo documentation](https://docs.celo.org/celo-codebase/protocol/identity/encrypted-cloud-backup) - -## Hierarchy - -* **Backup** - -## Index - -### Properties - -* [computationalHardening](_backup_.backup.md#optional-computationalhardening) -* [encryptedData](_backup_.backup.md#encrypteddata) -* [encryptedFuseKey](_backup_.backup.md#optional-encryptedfusekey) -* [environment](_backup_.backup.md#optional-environment) -* [metadata](_backup_.backup.md#optional-metadata) -* [nonce](_backup_.backup.md#nonce) -* [odisDomain](_backup_.backup.md#optional-odisdomain) -* [version](_backup_.backup.md#version) - -## Properties - -### `Optional` computationalHardening - -• **computationalHardening**? : *[ComputationalHardeningConfig](../modules/_config_.md#computationalhardeningconfig)* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:79](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L79)* - -Options for local computational hardening of the encryption key through PBKDF or scrypt. - -**`remarks`** Adding computational hardening provides a measure of security from password guessing -when the password has a moderate amount of entropy (e.g. a password generated under good -guidelines). If the user secret has very low entropy, such as with a 6-digit PIN, -computational hardening does not add significant security. - -___ - -### encryptedData - -• **encryptedData**: *Buffer* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:43](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L43)* - -AES-128-GCM encryption of the user's secret backup data. - -**`remarks`** The backup key is derived from the user's password or PIN hardened with input from the -ODIS rate-limited hashing service and optionally a circuit breaker service. - -___ - -### `Optional` encryptedFuseKey - -• **encryptedFuseKey**? : *Buffer* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:69](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L69)* - -RSA-OAEP-256 encryption of a randomly chosen 128-bit value, the fuse key. - -**`remarks`** The fuse key, if provided, is combined with the password in local key derivation. -Encryption is under the public key of the circuit breaker service. In order to get the fuseKey -the client will send this ciphertext to the circuit breaker service for decryption. - -___ - -### `Optional` environment - -• **environment**? : *undefined | object* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:105](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L105)* - -Information including the URL and public keys of the ODIS and circuit breaker services. - -___ - -### `Optional` metadata - -• **metadata**? : *undefined | object* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:102](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L102)* - -Data provided by the backup creator to identify the backup and its context - -**`remarks`** Metadata is provided by, and only meaningful to, the SDK user. The intention is for -this metadata to be used for identifying the backup and providing any context needed in the -application - -**`example`** -```typescript -{ - // Address of the primary account stored a backup of an account key. Used to display the - // balance and latest transaction information for a given backup. - accountAddress: string - // Unix timestamp used to indicate when the backup was created. - timestamp: number -} -``` - -___ - -### nonce - -• **nonce**: *Buffer* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:52](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L52)* - -A randomly chosen 256-bit value. Ensures uniqueness of the password derived encryption key. - -**`remarks`** The nonce value is appended to the password for local key derivation. It is also used -to derive an authentication key to include in the ODIS Domain for domain separation and to -ensure quota cannot be consumed by parties without access to the backup. - -___ - -### `Optional` odisDomain - -• **odisDomain**? : *SequentialDelayDomain* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:60](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L60)* - -ODIS Domain instance to be included in the query to ODIS for password hardening, - -**`remarks`** Currently only SequentialDelayDomain is supported. Other ODIS domains intended for key -hardening may be supported in the future. - -___ - -### version - -• **version**: *string* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:82](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L82)* - -Version number for the backup feature. Used to facilitate backwards compatibility. diff --git a/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.createbackupargs.md b/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.createbackupargs.md deleted file mode 100644 index 9a353cd890..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.createbackupargs.md +++ /dev/null @@ -1,48 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["backup"](../modules/_backup_.md) › [CreateBackupArgs](_backup_.createbackupargs.md) - -# Interface: CreateBackupArgs - -## Hierarchy - -* **CreateBackupArgs** - -## Index - -### Properties - -* [data](_backup_.createbackupargs.md#data) -* [hardening](_backup_.createbackupargs.md#hardening) -* [metadata](_backup_.createbackupargs.md#optional-metadata) -* [userSecret](_backup_.createbackupargs.md#usersecret) - -## Properties - -### data - -• **data**: *Buffer* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:223](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L223)* - -___ - -### hardening - -• **hardening**: *[HardeningConfig](_config_.hardeningconfig.md)* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:225](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L225)* - -___ - -### `Optional` metadata - -• **metadata**? : *undefined | object* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:226](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L226)* - -___ - -### userSecret - -• **userSecret**: *Buffer | string* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:224](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L224)* diff --git a/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.createpasswordencryptedbackupargs.md b/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.createpasswordencryptedbackupargs.md deleted file mode 100644 index 23fbf49036..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.createpasswordencryptedbackupargs.md +++ /dev/null @@ -1,48 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["backup"](../modules/_backup_.md) › [CreatePasswordEncryptedBackupArgs](_backup_.createpasswordencryptedbackupargs.md) - -# Interface: CreatePasswordEncryptedBackupArgs - -## Hierarchy - -* **CreatePasswordEncryptedBackupArgs** - -## Index - -### Properties - -* [data](_backup_.createpasswordencryptedbackupargs.md#data) -* [environment](_backup_.createpasswordencryptedbackupargs.md#optional-environment) -* [metadata](_backup_.createpasswordencryptedbackupargs.md#optional-metadata) -* [password](_backup_.createpasswordencryptedbackupargs.md#password) - -## Properties - -### data - -• **data**: *Buffer* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:171](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L171)* - -___ - -### `Optional` environment - -• **environment**? : *[EnvironmentIdentifier](../enums/_config_.environmentidentifier.md)* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:173](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L173)* - -___ - -### `Optional` metadata - -• **metadata**? : *undefined | object* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:174](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L174)* - -___ - -### password - -• **password**: *string* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:172](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L172)* diff --git a/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.createpinencryptedbackupargs.md b/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.createpinencryptedbackupargs.md deleted file mode 100644 index 39a49ec31a..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.createpinencryptedbackupargs.md +++ /dev/null @@ -1,48 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["backup"](../modules/_backup_.md) › [CreatePinEncryptedBackupArgs](_backup_.createpinencryptedbackupargs.md) - -# Interface: CreatePinEncryptedBackupArgs - -## Hierarchy - -* **CreatePinEncryptedBackupArgs** - -## Index - -### Properties - -* [data](_backup_.createpinencryptedbackupargs.md#data) -* [environment](_backup_.createpinencryptedbackupargs.md#optional-environment) -* [metadata](_backup_.createpinencryptedbackupargs.md#optional-metadata) -* [pin](_backup_.createpinencryptedbackupargs.md#pin) - -## Properties - -### data - -• **data**: *Buffer* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:112](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L112)* - -___ - -### `Optional` environment - -• **environment**? : *[EnvironmentIdentifier](../enums/_config_.environmentidentifier.md)* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:114](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L114)* - -___ - -### `Optional` metadata - -• **metadata**? : *undefined | object* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:115](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L115)* - -___ - -### pin - -• **pin**: *string* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:113](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L113)* diff --git a/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.openbackupargs.md b/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.openbackupargs.md deleted file mode 100644 index b749805265..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/interfaces/_backup_.openbackupargs.md +++ /dev/null @@ -1,30 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["backup"](../modules/_backup_.md) › [OpenBackupArgs](_backup_.openbackupargs.md) - -# Interface: OpenBackupArgs - -## Hierarchy - -* **OpenBackupArgs** - -## Index - -### Properties - -* [backup](_backup_.openbackupargs.md#backup) -* [userSecret](_backup_.openbackupargs.md#usersecret) - -## Properties - -### backup - -• **backup**: *[Backup](_backup_.backup.md)* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:383](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L383)* - -___ - -### userSecret - -• **userSecret**: *Buffer | string* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:384](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L384)* diff --git a/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.circuitbreakerconfig.md b/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.circuitbreakerconfig.md deleted file mode 100644 index 852469863f..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.circuitbreakerconfig.md +++ /dev/null @@ -1,25 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["config"](../modules/_config_.md) › [CircuitBreakerConfig](_config_.circuitbreakerconfig.md) - -# Interface: CircuitBreakerConfig - -Configuration for usage of a circuit breaker to protect the encryption keys - -## Hierarchy - -* **CircuitBreakerConfig** - -## Index - -### Properties - -* [environment](_config_.circuitbreakerconfig.md#environment) - -## Properties - -### environment - -• **environment**: *CircuitBreakerServiceContext* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:54](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L54)* - -Environment information including the URL and public key of the circuit breaker service diff --git a/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.hardeningconfig.md b/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.hardeningconfig.md deleted file mode 100644 index 99b7194c76..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.hardeningconfig.md +++ /dev/null @@ -1,51 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["config"](../modules/_config_.md) › [HardeningConfig](_config_.hardeningconfig.md) - -# Interface: HardeningConfig - -## Hierarchy - -* **HardeningConfig** - -## Index - -### Properties - -* [circuitBreaker](_config_.hardeningconfig.md#optional-circuitbreaker) -* [computational](_config_.hardeningconfig.md#optional-computational) -* [odis](_config_.hardeningconfig.md#optional-odis) - -## Properties - -### `Optional` circuitBreaker - -• **circuitBreaker**? : *[CircuitBreakerConfig](_config_.circuitbreakerconfig.md)* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:33](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L33)* - -If provided, a circuit breaker will be used with the given configuration to protect the backup key - -___ - -### `Optional` computational - -• **computational**? : *[ComputationalHardeningConfig](../modules/_config_.md#computationalhardeningconfig)* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:25](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L25)* - -If provided, a computational hardening function (e.g. scrypt or PBKDF2) will be applied to -locally harden the backup encryption key. - -**`remarks`** Recommended for password-encrypted backups, especially if a circuit breaker is not in -use, as this provides some degree of protection in the event of an ODIS compromise. When -generating backups on low-power devices (e.g. budget smart phones) and encrypting with -low-entropy secrets (e.g. 6-digit PINs) local hardening cannot offer significant protection. - -___ - -### `Optional` odis - -• **odis**? : *[OdisHardeningConfig](_config_.odishardeningconfig.md)* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:28](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L28)* - -If provided, ODIS will be used with the given configuration to harden the backup key diff --git a/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.odishardeningconfig.md b/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.odishardeningconfig.md deleted file mode 100644 index 2b7dd43502..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.odishardeningconfig.md +++ /dev/null @@ -1,40 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["config"](../modules/_config_.md) › [OdisHardeningConfig](_config_.odishardeningconfig.md) - -# Interface: OdisHardeningConfig - -Configuration for usage of ODIS to harden the encryption keys - -## Hierarchy - -* **OdisHardeningConfig** - -## Index - -### Properties - -* [environment](_config_.odishardeningconfig.md#environment) -* [rateLimit](_config_.odishardeningconfig.md#ratelimit) - -## Properties - -### environment - -• **environment**: *OdisServiceContext* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:48](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L48)* - -Environment information including the URL and public key of the ODIS service - -___ - -### rateLimit - -• **rateLimit**: *SequentialDelayStage[]* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:45](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L45)* - -Rate limiting information used to construct the ODIS domain which will be used to harden the -encryption key through ODIS' domain password hardening service. - -**`remarks`** Currently supports the SequentialDelayDomain. In the future, as additional domains are -standardized for key hardening, they may be added here to allow a wider range of configuration. diff --git a/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.pbkdfconfig.md b/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.pbkdfconfig.md deleted file mode 100644 index a4a51c36c9..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.pbkdfconfig.md +++ /dev/null @@ -1,30 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["config"](../modules/_config_.md) › [PbkdfConfig](_config_.pbkdfconfig.md) - -# Interface: PbkdfConfig - -## Hierarchy - -* **PbkdfConfig** - -## Index - -### Properties - -* [function](_config_.pbkdfconfig.md#function) -* [iterations](_config_.pbkdfconfig.md#iterations) - -## Properties - -### function - -• **function**: *[PBKDF](../enums/_config_.computationalhardeningfunction.md#pbkdf)* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:63](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L63)* - -___ - -### iterations - -• **iterations**: *number* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:64](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L64)* diff --git a/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.scryptconfig.md b/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.scryptconfig.md deleted file mode 100644 index 032d6f7369..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/interfaces/_config_.scryptconfig.md +++ /dev/null @@ -1,56 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["config"](../modules/_config_.md) › [ScryptConfig](_config_.scryptconfig.md) - -# Interface: ScryptConfig - -## Hierarchy - -* [ScryptOptions](_utils_.scryptoptions.md) - - ↳ **ScryptConfig** - -## Index - -### Properties - -* [blockSize](_config_.scryptconfig.md#optional-blocksize) -* [cost](_config_.scryptconfig.md#cost) -* [function](_config_.scryptconfig.md#function) -* [parallelization](_config_.scryptconfig.md#optional-parallelization) - -## Properties - -### `Optional` blockSize - -• **blockSize**? : *undefined | number* - -*Inherited from [ScryptOptions](_utils_.scryptoptions.md).[blockSize](_utils_.scryptoptions.md#optional-blocksize)* - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:117](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L117)* - -___ - -### cost - -• **cost**: *number* - -*Inherited from [ScryptOptions](_utils_.scryptoptions.md).[cost](_utils_.scryptoptions.md#cost)* - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:116](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L116)* - -___ - -### function - -• **function**: *[SCRYPT](../enums/_config_.computationalhardeningfunction.md#scrypt)* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:68](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L68)* - -___ - -### `Optional` parallelization - -• **parallelization**? : *undefined | number* - -*Inherited from [ScryptOptions](_utils_.scryptoptions.md).[parallelization](_utils_.scryptoptions.md#optional-parallelization)* - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:118](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L118)* diff --git a/packages/docs/sdk/docs/encrypted-backup/interfaces/_utils_.scryptoptions.md b/packages/docs/sdk/docs/encrypted-backup/interfaces/_utils_.scryptoptions.md deleted file mode 100644 index 9e3f345958..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/interfaces/_utils_.scryptoptions.md +++ /dev/null @@ -1,43 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["utils"](../modules/_utils_.md) › [ScryptOptions](_utils_.scryptoptions.md) - -# Interface: ScryptOptions - -Cost parameters for the scrypt computational hardening function. - -## Hierarchy - -* **ScryptOptions** - - ↳ [ScryptConfig](_config_.scryptconfig.md) - -## Index - -### Properties - -* [blockSize](_utils_.scryptoptions.md#optional-blocksize) -* [cost](_utils_.scryptoptions.md#cost) -* [parallelization](_utils_.scryptoptions.md#optional-parallelization) - -## Properties - -### `Optional` blockSize - -• **blockSize**? : *undefined | number* - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:117](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L117)* - -___ - -### cost - -• **cost**: *number* - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:116](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L116)* - -___ - -### `Optional` parallelization - -• **parallelization**? : *undefined | number* - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:118](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L118)* diff --git a/packages/docs/sdk/docs/encrypted-backup/modules/_backup_.md b/packages/docs/sdk/docs/encrypted-backup/modules/_backup_.md deleted file mode 100644 index e35cdaa443..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/modules/_backup_.md +++ /dev/null @@ -1,156 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["backup"](_backup_.md) - -# Module: "backup" - -## Index - -### Interfaces - -* [Backup](../interfaces/_backup_.backup.md) -* [CreateBackupArgs](../interfaces/_backup_.createbackupargs.md) -* [CreatePasswordEncryptedBackupArgs](../interfaces/_backup_.createpasswordencryptedbackupargs.md) -* [CreatePinEncryptedBackupArgs](../interfaces/_backup_.createpinencryptedbackupargs.md) -* [OpenBackupArgs](../interfaces/_backup_.openbackupargs.md) - -### Functions - -* [createBackup](_backup_.md#createbackup) -* [createPasswordEncryptedBackup](_backup_.md#createpasswordencryptedbackup) -* [createPinEncryptedBackup](_backup_.md#createpinencryptedbackup) -* [openBackup](_backup_.md#openbackup) - -## Functions - -### createBackup - -▸ **createBackup**(`__namedParameters`: object): *Promise‹Result‹[Backup](../interfaces/_backup_.backup.md), [BackupError](_errors_.md#backuperror)››* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:247](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L247)* - -Create a data backup, encrypting it with a hardened key derived from the given password or PIN. - -**`privateremarks`** Most of this functions code is devoted to key generation starting with the input -password or PIN and ending up with a hardened encryption key. It is important that the order and -inputs to each step in the derivation be well considered and implemented correctly. One important -requirement is that no output included in the backup acts as a "commitment" to the password or PIN -value, except the final ciphertext. An example of an issue with this would be if a hash of the -password and nonce were included in the backup. If a commitment to the password or PIN is -included, an attacker can locally brute force that commitment to recover the password, then use -that knowledge to complete the derivation. - -**Parameters:** - -▪ **__namedParameters**: *object* - -Name | Type | Description | ------- | ------ | ------ | -`data` | Buffer‹› | The secret data (e.g. BIP-39 mnemonic phrase) to be included in the encrypted backup. | -`hardening` | [HardeningConfig](../interfaces/_config_.hardeningconfig.md) | Configuration for how the password should be hardened in deriving the key. | -`metadata` | undefined | object | Arbitrary key-value data to include in the backup to identify it. | -`userSecret` | string | Buffer‹› | Password, PIN, or other user secret to use in deriving the encryption key. If a string is provided, it will be UTF-8 encoded into a Buffer before use. | - -**Returns:** *Promise‹Result‹[Backup](../interfaces/_backup_.backup.md), [BackupError](_errors_.md#backuperror)››* - -___ - -### createPasswordEncryptedBackup - -▸ **createPasswordEncryptedBackup**(`__namedParameters`: object): *Promise‹Result‹[Backup](../interfaces/_backup_.backup.md), [BackupError](_errors_.md#backuperror)››* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:202](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L202)* - -Create a data backup, encrypting it with a hardened key derived from the given password. - -**`remarks`** Because passwords have moderate entropy, the total number of guesses is restricted. - * The user initially gets 5 attempts without delay. - * Then the user gets two attempts every 5 seconds for up to 20 attempts. - * Then the user gets two attempts every 30 seconds for up to 20 attempts. - * Then the user gets two attempts every 5 minutes for up to 20 attempts. - * Then the user gets two attempts every hour for up to 20 attempts. - * Then the user gets two attempts every day for up to 20 attempts. - -Following guidelines in NIST-800-63-3 it is strongly recommended that the caller apply a password -blocklist to the users choice of password. - -In order to handle the event of an ODIS service compromise, this configuration additionally -hardens the password input with a computational hardening function. In particular, scrypt is used -with IETF recommended parameters [IETF recommended scrypt parameters](https://tools.ietf.org/id/draft-whited-kitten-password-storage-00.html#name-scrypt) - -**Parameters:** - -▪ **__namedParameters**: *object* - -Name | Type | Description | ------- | ------ | ------ | -`data` | Buffer‹› | The secret data (e.g. BIP-39 mnemonic phrase) to be included in the encrypted backup. | -`environment` | undefined | [MAINNET](../enums/_config_.environmentidentifier.md#mainnet) | [ALFAJORES](../enums/_config_.environmentidentifier.md#alfajores) | - | -`metadata` | undefined | object | Arbitrary key-value data to include in the backup to identify it. | -`password` | string | Password to use in deriving the encryption key. | - -**Returns:** *Promise‹Result‹[Backup](../interfaces/_backup_.backup.md), [BackupError](_errors_.md#backuperror)››* - -___ - -### createPinEncryptedBackup - -▸ **createPinEncryptedBackup**(`__namedParameters`: object): *Promise‹Result‹[Backup](../interfaces/_backup_.backup.md), [BackupError](_errors_.md#backuperror)››* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:150](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L150)* - -Create a data backup, encrypting it with a hardened key derived from the given PIN. - -**`remarks`** Using a 4 or 6 digit PIN for encryption requires an extremely restrictive rate limit for -attempts to guess the PIN. This is enforced by ODIS through the SequentialDelayDomain with -settings to allow the user (or an attacker) only a fixed number of attempts to guess their PIN. - -Because PINs have very little entropy, the total number of guesses is very restricted. - * On the first day, the client has 10 attempts. 5 within 10s. 5 more over roughly 45 minutes. - * On the second day, the client has 5 attempts over roughly 2 minutes. - * On the third day, the client has 3 attempts over roughly 40 seconds. - * On the fourth day, the client has 2 attempts over roughly 10 seconds. - * Overall, the client has 25 attempts over 4 days. All further attempts will be denied. - -It is strongly recommended that the calling application implement a PIN blocklist to prevent the -user from selecting a number of the most common PIN codes (e.g. blocking the top 25k PINs by -frequency of appearance in the HIBP Passwords dataset). An example implementation can be seen in -the Valora wallet. [PIN blocklist implementation](https://github.com/valora-inc/wallet/blob/3940661c40d08e4c5db952bd0abeaabb0030fc7a/packages/mobile/src/pincode/authentication.ts#L56-L108) - -In order to handle the event of an ODIS service compromise, this configuration additionally -includes a circuit breaker service run by Valora. In the event of an ODIS compromise, the Valora -team will take their service offline, preventing backups using the circuit breaker from being -opened. This ensures that an attacker who has compromised ODIS cannot leverage their attack to -forcibly open backups created with this function. - -**Parameters:** - -▪ **__namedParameters**: *object* - -Name | Type | Description | ------- | ------ | ------ | -`data` | Buffer‹› | The secret data (e.g. BIP-39 mnemonic phrase) to be included in the encrypted backup. | -`environment` | undefined | [MAINNET](../enums/_config_.environmentidentifier.md#mainnet) | [ALFAJORES](../enums/_config_.environmentidentifier.md#alfajores) | - | -`metadata` | undefined | object | Arbitrary key-value data to include in the backup to identify it. | -`pin` | string | PIN to use in deriving the encryption key. | - -**Returns:** *Promise‹Result‹[Backup](../interfaces/_backup_.backup.md), [BackupError](_errors_.md#backuperror)››* - -___ - -### openBackup - -▸ **openBackup**(`__namedParameters`: object): *Promise‹Result‹Buffer, [BackupError](_errors_.md#backuperror)››* - -*Defined in [packages/sdk/encrypted-backup/src/backup.ts:394](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/backup.ts#L394)* - -Open an encrypted backup file, using the provided password or PIN to derive the decryption key. - -**Parameters:** - -▪ **__namedParameters**: *object* - -Name | Type | Description | ------- | ------ | ------ | -`backup` | [Backup](../interfaces/_backup_.backup.md) | Backup structure including the ciphertext and key derivation information. | -`userSecret` | string | Buffer‹› | Password, PIN, or other user secret to use in deriving the encryption key. If a string is provided, it will be UTF-8 encoded into a Buffer before use. | - -**Returns:** *Promise‹Result‹Buffer, [BackupError](_errors_.md#backuperror)››* diff --git a/packages/docs/sdk/docs/encrypted-backup/modules/_config_.md b/packages/docs/sdk/docs/encrypted-backup/modules/_config_.md deleted file mode 100644 index f7cabd87e3..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/modules/_config_.md +++ /dev/null @@ -1,137 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["config"](_config_.md) - -# Module: "config" - -## Index - -### Enumerations - -* [ComputationalHardeningFunction](../enums/_config_.computationalhardeningfunction.md) -* [EnvironmentIdentifier](../enums/_config_.environmentidentifier.md) - -### Interfaces - -* [CircuitBreakerConfig](../interfaces/_config_.circuitbreakerconfig.md) -* [HardeningConfig](../interfaces/_config_.hardeningconfig.md) -* [OdisHardeningConfig](../interfaces/_config_.odishardeningconfig.md) -* [PbkdfConfig](../interfaces/_config_.pbkdfconfig.md) -* [ScryptConfig](../interfaces/_config_.scryptconfig.md) - -### Type aliases - -* [ComputationalHardeningConfig](_config_.md#computationalhardeningconfig) - -### Object literals - -* [PASSWORD_HARDENING_ALFAJORES_CONFIG](_config_.md#const-password_hardening_alfajores_config) -* [PASSWORD_HARDENING_MAINNET_CONFIG](_config_.md#const-password_hardening_mainnet_config) -* [PIN_HARDENING_ALFAJORES_CONFIG](_config_.md#const-pin_hardening_alfajores_config) -* [PIN_HARDENING_MAINNET_CONFIG](_config_.md#const-pin_hardening_mainnet_config) - -## Type aliases - -### ComputationalHardeningConfig - -Ƭ **ComputationalHardeningConfig**: *[PbkdfConfig](../interfaces/_config_.pbkdfconfig.md) | [ScryptConfig](../interfaces/_config_.scryptconfig.md)* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:71](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L71)* - -## Object literals - -### `Const` PASSWORD_HARDENING_ALFAJORES_CONFIG - -### ▪ **PASSWORD_HARDENING_ALFAJORES_CONFIG**: *object* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:289](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L289)* - -▪ **computational**: *object* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:294](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L294)* - -* **blockSize**: *number* = 8 - -* **cost**: *number* = 32768 - -* **function**: *[SCRYPT](../enums/_config_.computationalhardeningfunction.md#scrypt)* = ComputationalHardeningFunction.SCRYPT - -* **parallelization**: *number* = 1 - -▪ **odis**: *object* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:290](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L290)* - -* **environment**: *ServiceContext* = ODIS_ALFAJORES_CONTEXT - -* **rateLimit**: *SequentialDelayStage[]* = PASSWORD_HARDENING_RATE_LIMIT - -___ - -### `Const` PASSWORD_HARDENING_MAINNET_CONFIG - -### ▪ **PASSWORD_HARDENING_MAINNET_CONFIG**: *object* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:276](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L276)* - -▪ **computational**: *object* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:281](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L281)* - -* **blockSize**: *number* = 8 - -* **cost**: *number* = 32768 - -* **function**: *[SCRYPT](../enums/_config_.computationalhardeningfunction.md#scrypt)* = ComputationalHardeningFunction.SCRYPT - -* **parallelization**: *number* = 1 - -▪ **odis**: *object* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:277](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L277)* - -* **environment**: *ServiceContext* = ODIS_MAINNET_CONTEXT - -* **rateLimit**: *SequentialDelayStage[]* = PASSWORD_HARDENING_RATE_LIMIT - -___ - -### `Const` PIN_HARDENING_ALFAJORES_CONFIG - -### ▪ **PIN_HARDENING_ALFAJORES_CONFIG**: *object* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:266](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L266)* - -▪ **circuitBreaker**: *object* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:271](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L271)* - -* **environment**: *CircuitBreakerServiceContext* = VALORA_ALFAJORES_CIRCUIT_BREAKER_ENVIRONMENT - -▪ **odis**: *object* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:267](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L267)* - -* **environment**: *ServiceContext* = ODIS_ALFAJORES_CONTEXT - -* **rateLimit**: *SequentialDelayStage[]* = PIN_HARDENING_RATE_LIMIT - -___ - -### `Const` PIN_HARDENING_MAINNET_CONFIG - -### ▪ **PIN_HARDENING_MAINNET_CONFIG**: *object* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:256](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L256)* - -▪ **circuitBreaker**: *object* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:261](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L261)* - -* **environment**: *CircuitBreakerServiceContext* = VALORA_MAINNET_CIRCUIT_BREAKER_ENVIRONMENT - -▪ **odis**: *object* - -*Defined in [packages/sdk/encrypted-backup/src/config.ts:257](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/config.ts#L257)* - -* **environment**: *ServiceContext* = ODIS_MAINNET_CONTEXT - -* **rateLimit**: *SequentialDelayStage[]* = PIN_HARDENING_RATE_LIMIT diff --git a/packages/docs/sdk/docs/encrypted-backup/modules/_errors_.md b/packages/docs/sdk/docs/encrypted-backup/modules/_errors_.md deleted file mode 100644 index 7e484fbb3a..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/modules/_errors_.md +++ /dev/null @@ -1,36 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["errors"](_errors_.md) - -# Module: "errors" - -## Index - -### Enumerations - -* [BackupErrorTypes](../enums/_errors_.backuperrortypes.md) - -### Classes - -* [AuthorizationError](../classes/_errors_.authorizationerror.md) -* [DecodeError](../classes/_errors_.decodeerror.md) -* [DecryptionError](../classes/_errors_.decryptionerror.md) -* [EncryptionError](../classes/_errors_.encryptionerror.md) -* [FetchError](../classes/_errors_.fetcherror.md) -* [InvalidBackupError](../classes/_errors_.invalidbackuperror.md) -* [OdisRateLimitingError](../classes/_errors_.odisratelimitingerror.md) -* [OdisServiceError](../classes/_errors_.odisserviceerror.md) -* [OdisVerificationError](../classes/_errors_.odisverificationerror.md) -* [PbkdfError](../classes/_errors_.pbkdferror.md) -* [ScryptError](../classes/_errors_.scrypterror.md) -* [UsageError](../classes/_errors_.usageerror.md) - -### Type aliases - -* [BackupError](_errors_.md#backuperror) - -## Type aliases - -### BackupError - -Ƭ **BackupError**: *[AuthorizationError](../classes/_errors_.authorizationerror.md) | CircuitBreakerError | [DecodeError](../classes/_errors_.decodeerror.md) | [DecryptionError](../classes/_errors_.decryptionerror.md) | [EncryptionError](../classes/_errors_.encryptionerror.md) | [FetchError](../classes/_errors_.fetcherror.md) | [InvalidBackupError](../classes/_errors_.invalidbackuperror.md) | [OdisServiceError](../classes/_errors_.odisserviceerror.md) | [OdisRateLimitingError](../classes/_errors_.odisratelimitingerror.md) | [OdisVerificationError](../classes/_errors_.odisverificationerror.md) | [PbkdfError](../classes/_errors_.pbkdferror.md) | [ScryptError](../classes/_errors_.scrypterror.md) | [UsageError](../classes/_errors_.usageerror.md)* - -*Defined in [packages/sdk/encrypted-backup/src/errors.ts:92](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/errors.ts#L92)* diff --git a/packages/docs/sdk/docs/encrypted-backup/modules/_odis_.md b/packages/docs/sdk/docs/encrypted-backup/modules/_odis_.md deleted file mode 100644 index 90f7377e8d..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/modules/_odis_.md +++ /dev/null @@ -1,82 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["odis"](_odis_.md) - -# Module: "odis" - -## Index - -### Functions - -* [buildOdisDomain](_odis_.md#buildodisdomain) -* [odisHardenKey](_odis_.md#odishardenkey) -* [odisQueryAuthorizer](_odis_.md#odisqueryauthorizer) - -## Functions - -### buildOdisDomain - -▸ **buildOdisDomain**(`config`: [OdisHardeningConfig](../interfaces/_config_.odishardeningconfig.md), `authorizer`: Address, `salt?`: undefined | string): *SequentialDelayDomain* - -*Defined in [packages/sdk/encrypted-backup/src/odis.ts:50](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.ts#L50)* - -Builds an ODIS SequentialDelayDomain with the given hardening configuration. - -**Parameters:** - -Name | Type | Description | ------- | ------ | ------ | -`config` | [OdisHardeningConfig](../interfaces/_config_.odishardeningconfig.md) | - | -`authorizer` | Address | Address of the key that should authorize requests to ODIS. | -`salt?` | undefined | string | - | - -**Returns:** *SequentialDelayDomain* - -A SequentialDelayDomain with the provided rate limiting configuration. - -___ - -### odisHardenKey - -▸ **odisHardenKey**(`key`: Buffer, `domain`: SequentialDelayDomain, `environment`: OdisServiceContext, `wallet?`: [EIP712Wallet](_utils_.md#eip712wallet)): *Promise‹Result‹Buffer, [BackupError](_errors_.md#backuperror)››* - -*Defined in [packages/sdk/encrypted-backup/src/odis.ts:74](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.ts#L74)* - -Returns a hardened key derived from the input key material and a POPRF evaluation on that keying -material under the given rate limiting domain. - -**Parameters:** - -Name | Type | Description | ------- | ------ | ------ | -`key` | Buffer | Input key material which will be the blinded input to the ODIS POPRF. | -`domain` | SequentialDelayDomain | Rate limiting configuration and domain input to the ODIS POPRF. | -`environment` | OdisServiceContext | Information for the targeted ODIS environment. | -`wallet?` | [EIP712Wallet](_utils_.md#eip712wallet) | Wallet with access to the authorizer signing key specified in the domain input. Should be provided if the input domain is authenticated. | - -**Returns:** *Promise‹Result‹Buffer, [BackupError](_errors_.md#backuperror)››* - -___ - -### odisQueryAuthorizer - -▸ **odisQueryAuthorizer**(`nonce`: Buffer): *object* - -*Defined in [packages/sdk/encrypted-backup/src/odis.ts:158](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.ts#L158)* - -Derive from the nonce a private key and use it to instantiate a wallet for request signing - -**`remarks`** It is important that the auth key does not mix in entropy from the password value. If -it did, then the derived address and signatures would act as a commitment to the underlying -password value and would allow offline brute force attacks when combined with the other values -mixed into the key value. - -**Parameters:** - -Name | Type | ------- | ------ | -`nonce` | Buffer | - -**Returns:** *object* - -* **address**: *Address* - -* **wallet**: *[EIP712Wallet](_utils_.md#eip712wallet)* diff --git a/packages/docs/sdk/docs/encrypted-backup/modules/_odis_mock_.md b/packages/docs/sdk/docs/encrypted-backup/modules/_odis_mock_.md deleted file mode 100644 index bb93003cd2..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/modules/_odis_mock_.md +++ /dev/null @@ -1,33 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["odis.mock"](_odis_mock_.md) - -# Module: "odis.mock" - -## Index - -### Classes - -* [MockOdis](../classes/_odis_mock_.mockodis.md) - -### Object literals - -* [MOCK_ODIS_ENVIRONMENT](_odis_mock_.md#const-mock_odis_environment) - -## Object literals - -### `Const` MOCK_ODIS_ENVIRONMENT - -### ▪ **MOCK_ODIS_ENVIRONMENT**: *object* - -*Defined in [packages/sdk/encrypted-backup/src/odis.mock.ts:23](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.mock.ts#L23)* - -### odisPubKey - -• **odisPubKey**: *string* = Buffer.from(MOCK_ODIS_KEYPAIR.publicKey).toString('base64') - -*Defined in [packages/sdk/encrypted-backup/src/odis.mock.ts:25](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.mock.ts#L25)* - -### odisUrl - -• **odisUrl**: *string* = "https://mockodis.com" - -*Defined in [packages/sdk/encrypted-backup/src/odis.mock.ts:24](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/odis.mock.ts#L24)* diff --git a/packages/docs/sdk/docs/encrypted-backup/modules/_schema_.md b/packages/docs/sdk/docs/encrypted-backup/modules/_schema_.md deleted file mode 100644 index 07bc7e930f..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/modules/_schema_.md +++ /dev/null @@ -1,122 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["schema"](_schema_.md) - -# Module: "schema" - -## Index - -### Variables - -* [BackupSchema](_schema_.md#const-backupschema) -* [BufferFromBase64](_schema_.md#const-bufferfrombase64) - -### Functions - -* [deserializeBackup](_schema_.md#deserializebackup) -* [serializeBackup](_schema_.md#serializebackup) - -## Variables - -### `Const` BackupSchema - -• **BackupSchema**: *Type‹[Backup](../interfaces/_backup_.backup.md), object›* = t.intersection([ - // Required fields - t.type({ - encryptedData: BufferFromBase64, - nonce: BufferFromBase64, - version: t.string, - }), - // Optional fields - // https://github.com/gcanti/io-ts/blob/master/index.md#mixing-required-and-optional-props - t.partial({ - odisDomain: SequentialDelayDomainSchema, - metadata: t.UnknownRecord, - encryptedFuseKey: BufferFromBase64, - computationalHardening: t.union([ - t.type({ - function: t.literal(ComputationalHardeningFunction.PBKDF), - iterations: t.number, - }), - t.intersection([ - t.type({ - function: t.literal(ComputationalHardeningFunction.SCRYPT), - cost: t.number, - }), - t.partial({ - blockSize: t.number, - parallelization: t.number, - }), - ]), - ]), - environment: t.partial({ - odis: t.type({ - odisUrl: t.string, - odisPubKey: t.string, - }), - circuitBreaker: t.type({ - url: t.string, - publicKey: t.string, - }), - }), - }), -]) - -*Defined in [packages/sdk/encrypted-backup/src/schema.ts:31](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/schema.ts#L31)* - -io-ts codec used to encode and decode backups from JSON objects - -___ - -### `Const` BufferFromBase64 - -• **BufferFromBase64**: *Type‹Buffer‹›, string, unknown›* = new t.Type( - 'BufferFromBase64', - Buffer.isBuffer, - (unk: unknown, context: t.Context) => - pipe( - t.string.validate(unk, context), - chain((str: string) => { - // Check that the string is base64 data and return the decoding if it is. - if (!BASE64_REGEXP.test(str)) { - return t.failure(unk, context, 'provided string is not base64') - } - return t.success(Buffer.from(str, 'base64')) - }) - ), - (buffer: Buffer) => buffer.toString('base64') -) - -*Defined in [packages/sdk/encrypted-backup/src/schema.ts:13](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/schema.ts#L13)* - -Utility type to leverage io-ts for encoding and decoding of buffers from base64 strings. - -## Functions - -### deserializeBackup - -▸ **deserializeBackup**(`data`: string): *Result‹[Backup](../interfaces/_backup_.backup.md), [DecodeError](../classes/_errors_.decodeerror.md)›* - -*Defined in [packages/sdk/encrypted-backup/src/schema.ts:77](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/schema.ts#L77)* - -**Parameters:** - -Name | Type | ------- | ------ | -`data` | string | - -**Returns:** *Result‹[Backup](../interfaces/_backup_.backup.md), [DecodeError](../classes/_errors_.decodeerror.md)›* - -___ - -### serializeBackup - -▸ **serializeBackup**(`backup`: [Backup](../interfaces/_backup_.backup.md)): *string* - -*Defined in [packages/sdk/encrypted-backup/src/schema.ts:73](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/schema.ts#L73)* - -**Parameters:** - -Name | Type | ------- | ------ | -`backup` | [Backup](../interfaces/_backup_.backup.md) | - -**Returns:** *string* diff --git a/packages/docs/sdk/docs/encrypted-backup/modules/_utils_.md b/packages/docs/sdk/docs/encrypted-backup/modules/_utils_.md deleted file mode 100644 index fda5c4bd23..0000000000 --- a/packages/docs/sdk/docs/encrypted-backup/modules/_utils_.md +++ /dev/null @@ -1,169 +0,0 @@ -[@celo/encrypted-backup](../README.md) › ["utils"](_utils_.md) - -# Module: "utils" - -## Index - -### Enumerations - -* [KDFInfo](../enums/_utils_.kdfinfo.md) - -### Interfaces - -* [ScryptOptions](../interfaces/_utils_.scryptoptions.md) - -### Type aliases - -* [EIP712Wallet](_utils_.md#eip712wallet) - -### Functions - -* [computationalHardenKey](_utils_.md#computationalhardenkey) -* [decrypt](_utils_.md#decrypt) -* [deriveKey](_utils_.md#derivekey) -* [encrypt](_utils_.md#encrypt) -* [pbkdf2](_utils_.md#pbkdf2) -* [scrypt](_utils_.md#scrypt) - -## Type aliases - -### EIP712Wallet - -Ƭ **EIP712Wallet**: *Pick‹ReadOnlyWallet, "getAccounts" | "hasAccount" | "signTypedData"›* - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:11](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L11)* - -Pared down ReadOnlyWallet type that supports the required functions of EIP-712 signing. - -## Functions - -### computationalHardenKey - -▸ **computationalHardenKey**(`key`: Buffer, `config`: [ComputationalHardeningConfig](_config_.md#computationalhardeningconfig)): *Promise‹Result‹Buffer, [PbkdfError](../classes/_errors_.pbkdferror.md) | [ScryptError](../classes/_errors_.scrypterror.md)››* - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:148](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L148)* - -**Parameters:** - -Name | Type | ------- | ------ | -`key` | Buffer | -`config` | [ComputationalHardeningConfig](_config_.md#computationalhardeningconfig) | - -**Returns:** *Promise‹Result‹Buffer, [PbkdfError](../classes/_errors_.pbkdferror.md) | [ScryptError](../classes/_errors_.scrypterror.md)››* - -___ - -### decrypt - -▸ **decrypt**(`key`: Buffer, `ciphertext`: Buffer): *Result‹Buffer, [DecryptionError](../classes/_errors_.decryptionerror.md)›* - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:66](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L66)* - -AES-256-GCM decrypt the given data with the given 32-byte key. -Ciphertext should be encoded as { iv || data || auth tag }. - -**Parameters:** - -Name | Type | ------- | ------ | -`key` | Buffer | -`ciphertext` | Buffer | - -**Returns:** *Result‹Buffer, [DecryptionError](../classes/_errors_.decryptionerror.md)›* - -___ - -### deriveKey - -▸ **deriveKey**(`info`: [KDFInfo](../enums/_utils_.kdfinfo.md), `sources`: Buffer[]): *Buffer* - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:34](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L34)* - -Key derivation function for mixing source keying material. - -**`remarks`** This function does not add any hardening to the input keying material. It is used only -to mix the provided key material sources. It's output should not be used to directly derive a key -from a password or other low entropy sources. - -**Parameters:** - -Name | Type | Description | ------- | ------ | ------ | -`info` | [KDFInfo](../enums/_utils_.kdfinfo.md) | Fixed string value used for domain separation. | -`sources` | Buffer[] | An array of keying material source values (e.g. a password and a nonce). | - -**Returns:** *Buffer* - -___ - -### encrypt - -▸ **encrypt**(`key`: Buffer, `data`: Buffer): *Result‹Buffer, [EncryptionError](../classes/_errors_.encryptionerror.md)›* - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:51](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L51)* - -AES-256-GCM encrypt the given data with the given 32-byte key. -Encode the ciphertext as { iv || data || auth tag } - -**Parameters:** - -Name | Type | ------- | ------ | -`key` | Buffer | -`data` | Buffer | - -**Returns:** *Result‹Buffer, [EncryptionError](../classes/_errors_.encryptionerror.md)›* - -___ - -### pbkdf2 - -▸ **pbkdf2**(`key`: Buffer, `iterations`: number): *Promise‹Result‹Buffer, [PbkdfError](../classes/_errors_.pbkdferror.md)››* - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:103](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L103)* - -PBKDF2-SHA256 computational key hardening. - -**`remarks`** When possible, a memory hard function such as scrypt should be used instead. -No salt parameter is provided as the intended use case of this function is to harden a -key value which is derived from a password but already has the salt mixed in. - -**`see`** { @link -https://nodejs.org/api/crypto.html#cryptopbkdf2password-salt-iterations-keylen-digest-callback | -NodeJS crypto.pbkdf2 API } - -**Parameters:** - -Name | Type | Description | ------- | ------ | ------ | -`key` | Buffer | Key buffer to compute hardening against. Should have a salt or nonce mixed in. | -`iterations` | number | Number of PBKDF2 iterations to execute for key hardening. | - -**Returns:** *Promise‹Result‹Buffer, [PbkdfError](../classes/_errors_.pbkdferror.md)››* - -___ - -### scrypt - -▸ **scrypt**(`key`: Buffer, `options`: [ScryptOptions](../interfaces/_utils_.scryptoptions.md)): *Promise‹Result‹Buffer, [ScryptError](../classes/_errors_.scrypterror.md)››* - -*Defined in [packages/sdk/encrypted-backup/src/utils.ts:134](https://github.com/celo-org/celo-monorepo/blob/master/packages/sdk/encrypted-backup/src/utils.ts#L134)* - -scrypt computational key hardening. - -**`remarks`** No salt parameter is provided as the intended use case of this function is to harden a -key value which is derived from a password but already has the salt mixed in. - -**`see`** { @link -https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback | -NodeJS crypto.scrypt API } - -**Parameters:** - -Name | Type | Description | ------- | ------ | ------ | -`key` | Buffer | Key buffer to compute hardening against. Should have a salt or nonce mixed in. | -`options` | [ScryptOptions](../interfaces/_utils_.scryptoptions.md) | Options to control the cost of the scrypt function. | - -**Returns:** *Promise‹Result‹Buffer, [ScryptError](../classes/_errors_.scrypterror.md)››* diff --git a/packages/env-tests/package.json b/packages/env-tests/package.json index 732839e6f9..3215ca64b8 100644 --- a/packages/env-tests/package.json +++ b/packages/env-tests/package.json @@ -9,7 +9,6 @@ "@celo/utils": "5.0.4", "@celo/base": "5.0.4", "@celo/connect": "5.0.4", - "@celo/identity": "5.0.4", "@celo/phone-utils": "5.0.4", "@celo/cryptographic-utils": "5.0.4", "bignumber.js": "^9.0.0", diff --git a/packages/phone-number-privacy/README.md b/packages/phone-number-privacy/README.md deleted file mode 100644 index 1cef4c0a07..0000000000 --- a/packages/phone-number-privacy/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Contents - -Home for the Oblivious Decentralized Identifier Service (ODIS), formerly PGPNP (Pretty Good Phone Number Privacy). diff --git a/packages/phone-number-privacy/TODO.md b/packages/phone-number-privacy/TODO.md deleted file mode 100644 index 2cefc1a3f0..0000000000 --- a/packages/phone-number-privacy/TODO.md +++ /dev/null @@ -1,9 +0,0 @@ -# TODO - -- Add caching to Combiner -- Fix Combiner e2e tests -- Fix types in errorResult and sendFailure so we don't have to use ANY in Signer -- Refactor domain sign handler to use db transactions properly -- refactor authorization function with the new account model -- Make caching config parameters configurable by environment -- TODO comments \ No newline at end of file diff --git a/packages/phone-number-privacy/combiner/.env b/packages/phone-number-privacy/combiner/.env deleted file mode 100644 index a764836363..0000000000 --- a/packages/phone-number-privacy/combiner/.env +++ /dev/null @@ -1,12 +0,0 @@ -# Options: json, human (default), stackdriver -LOG_FORMAT=stackdriver -# Options: fatal, error, warn, info (default), debug, trace -LOG_LEVEL=trace -SERVICE_NAME='odis-combiner' -# For e2e Tests -ODIS_BLOCKCHAIN_PROVIDER=https://alfajores-forno.celo-testnet.org -CONTEXT_NAME='alfajores' -# TODO investigate why these are defined here -NODE_OPTIONS='--require ./dist/tracing.js' -TRACER_ENDPOINT='https://grafana-agent.staging-odis2-centralus.celo-networks-dev.org/api/traces' -TRACING_SERVICE_NAME='odis-combiner-staging' diff --git a/packages/phone-number-privacy/combiner/.firebaserc b/packages/phone-number-privacy/combiner/.firebaserc deleted file mode 100644 index b8893af884..0000000000 --- a/packages/phone-number-privacy/combiner/.firebaserc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "projects": { - "default": "celo-phone-number-privacy" - } -} diff --git a/packages/phone-number-privacy/combiner/.gitignore b/packages/phone-number-privacy/combiner/.gitignore deleted file mode 100644 index f929bc6f85..0000000000 --- a/packages/phone-number-privacy/combiner/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -dist/ - -# Firebase cache -.firebase/ - diff --git a/packages/phone-number-privacy/combiner/README.md b/packages/phone-number-privacy/combiner/README.md deleted file mode 100644 index 5c9909368f..0000000000 --- a/packages/phone-number-privacy/combiner/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# ODIS Combiner - -A firebase function that orchestrates distributed BLS threshold signing with the set of ODIS signers. - -## DB Migrations - -### Add migration file - -To update the combiner DB schema, first run `yarn db:migrate:make ` to create a new migrations file. Then, fill in the new migration file as needed using the previous migration files as references. - -### Whitelist your IP address - -Go to [https://console.cloud.google.com/sql/instances?authuser=1&folder=&organizationId=&project=celo-phone-number-privacy] and navigate to the desired workspace and db instance. Then, add your IP address under `Connections -> Authorized networks`. - -Remember to remove your IP address from the whitelist when finished. - -### Add db credentials to config.ts - -Run the command `yarn config:get:` to fetch the necessary db credentials and add them to `src/config.ts` under the `DEV_MODE` section. DO NOT COMMIT THESE CREDENTIALS TO GITHUB. - -Note: When you fill in the `host` field you may need to use the database's public IP, which can be found in the `Overview` section under the link above. - -### Run migrations - -Always run migrations in staging first and ensure all e2e tests pass before migrating in alfajores and mainnet. - -Run `yarn db:migrate:` diff --git a/packages/phone-number-privacy/combiner/firebase.json b/packages/phone-number-privacy/combiner/firebase.json deleted file mode 100644 index 028b3af257..0000000000 --- a/packages/phone-number-privacy/combiner/firebase.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "functions": { - "source": ".", - "predeploy": ["yarn run lint", "yarn run build"] - } -} diff --git a/packages/phone-number-privacy/combiner/index.d.ts b/packages/phone-number-privacy/combiner/index.d.ts deleted file mode 100644 index 102dc17cf1..0000000000 --- a/packages/phone-number-privacy/combiner/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'bunyan-debug-stream' diff --git a/packages/phone-number-privacy/combiner/jest.config.js b/packages/phone-number-privacy/combiner/jest.config.js deleted file mode 100644 index ac1faeacbb..0000000000 --- a/packages/phone-number-privacy/combiner/jest.config.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - preset: 'ts-jest', - coverageReporters: [['lcov', { projectRoot: '../../../' }], 'text'], - collectCoverageFrom: ['./src/**'], - coverageThreshold: { - global: { - lines: 80, - }, - }, -} diff --git a/packages/phone-number-privacy/combiner/jest_setup.ts b/packages/phone-number-privacy/combiner/jest_setup.ts deleted file mode 100644 index d9933443ac..0000000000 --- a/packages/phone-number-privacy/combiner/jest_setup.ts +++ /dev/null @@ -1,6 +0,0 @@ -if (typeof window !== 'object') { - // @ts-ignore - global.window = global - // @ts-ignore - global.window.navigator = {} -} diff --git a/packages/phone-number-privacy/combiner/package.json b/packages/phone-number-privacy/combiner/package.json deleted file mode 100644 index 47e7b4d68a..0000000000 --- a/packages/phone-number-privacy/combiner/package.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "name": "@celo/phone-number-privacy-combiner", - "version": "3.0.1", - "description": "Orchestrates and combines threshold signatures for use in ODIS", - "author": "Celo", - "license": "Apache-2.0", - "main": "dist/index.js", - "scripts": { - "dev": "yarn build && firebase serve --only functions", - "deploy": "yarn build && firebase deploy --only functions:combiner", - "deploy:staging": "yarn deploy --project celo-phone-number-privacy-stg", - "deploy:alfajores": "yarn deploy --project celo-phone-number-privacy", - "deploy:mainnet": "yarn deploy --project celo-pgpnp-mainnet", - "config:get:staging": "firebase functions:config:get --project celo-phone-number-privacy-stg", - "config:get:alfajores": "firebase functions:config:get --project celo-phone-number-privacy", - "config:get:mainnet": "firebase functions:config:get --project celo-pgpnp-mainnet", - "config:set:staging": "firebase functions:config:set --project celo-phone-number-privacy-stg", - "config:set:alfajores": "firebase functions:config:set --project celo-phone-number-privacy", - "config:set:mainnet": "firebase functions:config:set --project celo-pgpnp-mainnet", - "clean": "tsc -b . --clean", - "build": "tsc -b .", - "lint": "tslint --project .", - "test": "jest --runInBand --testPathIgnorePatterns test/end-to-end", - "test:coverage": "yarn test --coverage", - "test:integration": "jest --runInBand test/integration", - "test:e2e": "jest --runInBand test/end-to-end --verbose", - "test:e2e:staging": "CONTEXT_NAME=staging yarn test:e2e", - "test:e2e:alfajores": "CONTEXT_NAME=alfajores yarn test:e2e", - "test:e2e:mainnet": "CONTEXT_NAME=mainnet yarn test:e2e" - }, - "dependencies": { - "@celo/contractkit": "^5.0.4", - "@celo/phone-number-privacy-common": "^3.0.3", - "@celo/identity": "^5.0.4", - "@celo/encrypted-backup": "^5.0.4", - "@celo/poprf": "^0.1.9", - "@types/bunyan": "^1.8.8", - "@opentelemetry/api": "^1.4.1", - "@opentelemetry/resources": "1.17.0", - "@opentelemetry/instrumentation": "^0.41.2", - "@opentelemetry/auto-instrumentations-node": "^0.38.0", - "@opentelemetry/exporter-jaeger": "^1.15.2", - "@opentelemetry/propagator-ot-trace": "^0.27.0", - "@opentelemetry/sdk-metrics": "^1.15.2", - "@opentelemetry/sdk-node": "^0.41.2", - "@opentelemetry/sdk-trace-web": "^1.15.2", - "@opentelemetry/sdk-trace-node": "1.15.2", - "@opentelemetry/sdk-trace-base": "^1.17.0", - "@opentelemetry/semantic-conventions": "^1.15.2", - "blind-threshold-bls": "npm:@celo/blind-threshold-bls@1.0.0-beta", - "dotenv": "^8.2.0", - "io-ts": "2.0.1", - "bunyan": "1.8.12", - "express": "^4.17.1", - "firebase-admin": "^11.10.1", - "firebase-functions": "^4.4.1", - "knex": "^2.1.0", - "node-fetch": "^2.6.9", - "pg": "^8.2.1", - "uuid": "^7.0.3" - }, - "devDependencies": { - "@types/node": "18.15.13", - "@celo/utils": "^5.0.4", - "@celo/phone-utils": "^5.0.4", - "@types/express": "^4.17.6", - "@types/supertest": "^2.0.12", - "@types/uuid": "^7.0.3", - "firebase-functions-test": "^3.1.0", - "firebase-tools": "12.4.7" - }, - "engines": { - "node": "18" - } -} \ No newline at end of file diff --git a/packages/phone-number-privacy/combiner/src/common/combine.ts b/packages/phone-number-privacy/combiner/src/common/combine.ts deleted file mode 100644 index 2f10b5a2d6..0000000000 --- a/packages/phone-number-privacy/combiner/src/common/combine.ts +++ /dev/null @@ -1,233 +0,0 @@ -import { - ErrorMessage, - KeyVersionInfo, - OdisRequest, - OdisResponse, - responseHasExpectedKeyVersion, - SignerEndpoint, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Request } from 'express' -import * as t from 'io-ts' -import { PerformanceObserver } from 'perf_hooks' -import { fetchSignerResponseWithFallback, SignerResponse } from './io' - -export interface Signer { - url: string - fallbackUrl?: string -} - -export interface ThresholdCallToSignersOptions { - signers: Signer[] - endpoint: SignerEndpoint - requestTimeoutMS: number - shouldCheckKeyVersion: boolean - keyVersionInfo: KeyVersionInfo - request: Request<{}, {}, R> - responseSchema: t.Type, OdisResponse, unknown> -} - -export async function thresholdCallToSigners( - logger: Logger, - options: ThresholdCallToSignersOptions, - processResult: (res: OdisResponse) => Promise = (_) => Promise.resolve(false) -): Promise<{ signerResponses: Array>; maxErrorCode?: number }> { - const obs = new PerformanceObserver((list) => { - // Possible race condition here: if multiple signers take exactly the same - // amount of time, the PerformanceObserver callback may be called twice with - // both entries present. Node 12 doesn't allow for entries to be deleted by name, - // and eliminating the race condition requires a more significant redesign of - // the measurement code. - // This is only used for monitoring purposes, so a rare - // duplicate latency measure for the signer should have minimal impact. - list.getEntries().forEach((entry) => { - logger.info({ latency: entry, signer: entry.name }, 'Signer response latency measured') - }) - }) - obs.observe({ entryTypes: ['measure'], buffered: false }) - - const { - signers, - endpoint, - requestTimeoutMS, - shouldCheckKeyVersion, - keyVersionInfo, - request, - responseSchema, - } = options - - const manualAbort = new AbortController() - const timeoutSignal = AbortSignal.timeout(requestTimeoutMS) - const abortSignal = abortSignalAny([manualAbort.signal, timeoutSignal]) - - let errorCount = 0 - const errorCodes: Map = new Map() - - const requiredThreshold = keyVersionInfo.threshold - - const responses: Array> = [] - // Forward request to signers - // An unexpected error in handling the result for one signer should not - // block a threshold of correct responses, but should be logged. - await Promise.all( - signers.map(async (signer) => { - try { - const signerFetchResult = await fetchSignerResponseWithFallback( - signer, - endpoint, - keyVersionInfo.keyVersion, - request, - logger, - // @ts-ignore - abortSignal - ) - - // used for log based metrics - logger.info({ - message: 'Received signerFetchResult', - signer: signer.url, - status: signerFetchResult.status, - }) - - if (!signerFetchResult.ok) { - // used for log based metrics - logger.info({ - message: 'Received signerFetchResult on unsuccessful signer response', - res: await signerFetchResult.json(), - status: signerFetchResult.status, - signer: signer.url, - }) - - errorCount++ - errorCodes.set( - signerFetchResult.status, - (errorCodes.get(signerFetchResult.status) ?? 0) + 1 - ) - - if (signers.length - errorCount < requiredThreshold) { - logger.warn('Not possible to reach a threshold of signer responses. Failing fast') - manualAbort.abort() - } - return - } - - if ( - shouldCheckKeyVersion && - !responseHasExpectedKeyVersion(signerFetchResult, keyVersionInfo.keyVersion, logger) - ) { - throw new Error(ErrorMessage.INVALID_KEY_VERSION_RESPONSE) - } - - const data: any = await signerFetchResult.json() - logger.info( - { signer, res: data, status: signerFetchResult.status }, - `received 'OK' response from signer` - ) - - const odisResponse: OdisResponse = parseSchema(responseSchema, data, logger) - if (!odisResponse.success) { - logger.error( - { err: odisResponse.error, signer: signer.url }, - `Signer request to failed with 'OK' status` - ) - throw new Error(ErrorMessage.SIGNER_RESPONSE_FAILED_WITH_OK_STATUS) - } - - responses.push({ res: odisResponse, url: signer.url }) - - if (await processResult(odisResponse)) { - // we already have enough responses - manualAbort.abort() - } - } catch (err) { - if (isTimeoutError(err)) { - logger.error({ signer }, ErrorMessage.TIMEOUT_FROM_SIGNER) - } else if (isAbortError(err)) { - logger.info({ signer }, WarningMessage.CANCELLED_REQUEST_TO_SIGNER) - } else { - // Logging the err & message simultaneously fails to log the message in some cases - logger.error({ signer }, ErrorMessage.SIGNER_REQUEST_ERROR) - logger.error({ signer, err }) - - errorCount++ - if (signers.length - errorCount < requiredThreshold) { - logger.warn('Not possible to reach a threshold of signer responses. Failing fast') - manualAbort.abort() - } - } - } - }) - ) - - // DO NOT call performance.clearMarks() as this also deletes marks used to - // measure e2e combiner latency. - obs.disconnect() - - if (errorCodes.size > 0) { - if (errorCodes.size > 1) { - logger.error( - { errorCodes: JSON.stringify([...errorCodes]) }, - ErrorMessage.INCONSISTENT_SIGNER_RESPONSES - ) - } - - return { signerResponses: responses, maxErrorCode: getMajorityErrorCode(errorCodes) } - } else { - return { signerResponses: responses } - } -} - -function parseSchema(schema: t.Type, data: unknown, logger: Logger): T { - if (!schema.is(data)) { - logger.error({ data }, `Malformed schema`) - throw new Error(ErrorMessage.INVALID_SIGNER_RESPONSE) - } - return data -} - -function isTimeoutError(err: unknown) { - return err instanceof Error && err.name === 'TimeoutError' -} - -export function isAbortError(err: unknown) { - return err instanceof Error && err.name === 'AbortError' -} - -function getMajorityErrorCode(errorCodes: Map): number { - let maxErrorCode = -1 - let maxCount = -1 - errorCodes.forEach((count, errorCode) => { - // This gives priority to the lower status codes in the event of a tie - // because 400s are more helpful than 500s for user feedback - if (count > maxCount || (count === maxCount && errorCode < maxErrorCode)) { - maxCount = count - maxErrorCode = errorCode - } - }) - return maxErrorCode -} - -/* - * TODO remove this in favor of actual implementation once we can upgrade to node v18.17.0. - * The Combiner cannot currently be deployed with node versions beyond v18. - * Actual implementation: - * https://github.com/nodejs/node/blob/5ff1ead6b2d6da7ba044b11e2824c7cbf5a94cb8/lib/internal/abort_controller.js#L198C24-L198C24 - */ -function abortSignalAny(signals: AbortSignal[]): AbortSignal { - const ac = new AbortController() - for (const signal of signals) { - if (signal.aborted) { - ac.abort(signal) - return ac.signal - } - signal.addEventListener( - 'abort', - (e) => { - ac.abort(e) - }, - { once: true } - ) - } - return ac.signal -} diff --git a/packages/phone-number-privacy/combiner/src/common/crypto-clients/bls-crypto-client.ts b/packages/phone-number-privacy/combiner/src/common/crypto-clients/bls-crypto-client.ts deleted file mode 100644 index 40a2d8798a..0000000000 --- a/packages/phone-number-privacy/combiner/src/common/crypto-clients/bls-crypto-client.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { ErrorMessage } from '@celo/phone-number-privacy-common' -import threshold_bls from 'blind-threshold-bls' -import Logger from 'bunyan' -import { CryptoClient, ServicePartialSignature } from './crypto-client' - -function flattenSigsArray(sigs: Uint8Array[]) { - return Uint8Array.from(sigs.reduce((a, b) => a.concat(Array.from(b)), [] as any)) -} -export class BLSCryptographyClient extends CryptoClient { - // Signatures can be verified server-side without knowledge of the blinding factor - private verifiedSignatures: ServicePartialSignature[] = [] - - protected get allSignaturesLength(): number { - return this.unverifiedSignatures.length + this.verifiedSignatures.length - } - - private get allSignatures(): Uint8Array { - const allSigs = this.verifiedSignatures.concat(this.unverifiedSignatures) - const sigBuffers = allSigs.map((response) => Buffer.from(response.signature, 'base64')) - return flattenSigsArray(sigBuffers) - } - - /* - * Computes the BLS signature for the blinded phone number. - * On error, logs and throws exception for not enough signatures, - * and drops the invalid signature for future requests using this instance. - */ - protected _combineBlindedSignatureShares(blindedMessage: string, logger: Logger): string { - // Optimistically attempt to combine unverified signatures - // If combination or verification fails, iterate through each signature and remove invalid ones - // We do this since partial signature verification incurs higher latencies - try { - const result = threshold_bls.combine(this.keyVersionInfo.threshold, this.allSignatures) - this.verifyCombinedSignature(blindedMessage, result, logger) - return Buffer.from(result).toString('base64') - } catch (error) { - logger.error(error) - // Verify each signature and remove invalid ones - // This logging will help us troubleshoot which signers are having issues - this.unverifiedSignatures.forEach((unverifiedSignature) => { - this.verifyPartialSignature(blindedMessage, unverifiedSignature, logger) - }) - this.clearUnverifiedSignatures() - throw new Error(ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES) - } - } - - private verifyCombinedSignature( - blindedMessage: string, - combinedSignature: Uint8Array, - logger: Logger - ) { - try { - // TODO: Address bad documentation in threshold-bls lib. - // Documentation should not specify that verifyBlindSignature verifies the - // signature after it has been unblinded. - threshold_bls.verifyBlindSignature( - Buffer.from(this.keyVersionInfo.pubKey, 'base64'), - Buffer.from(blindedMessage, 'base64'), - combinedSignature - ) - } catch (error) { - logger.error('Combined signature verification failed') - throw error - } - } - - private verifyPartialSignature( - blindedMessage: string, - unverifiedSignature: ServicePartialSignature, - logger: Logger - ) { - const sigBuffer = Buffer.from(unverifiedSignature.signature, 'base64') - if (this.isValidPartialSignature(sigBuffer, blindedMessage)) { - // We move it to the verified set so that we don't need to re-verify in the future - this.verifiedSignatures.push(unverifiedSignature) - } else { - logger.error({ url: unverifiedSignature.url }, ErrorMessage.VERIFY_PARITAL_SIGNATURE_ERROR) - } - } - - private clearUnverifiedSignatures() { - this.unverifiedSignatures = [] - } - - private isValidPartialSignature(signature: Buffer, blindedMessage: string) { - try { - threshold_bls.partialVerifyBlindSignature( - Buffer.from(this.keyVersionInfo.polynomial, 'hex'), - Buffer.from(blindedMessage, 'base64'), - signature - ) - return true - } catch { - return false - } - } -} diff --git a/packages/phone-number-privacy/combiner/src/common/crypto-clients/crypto-client.ts b/packages/phone-number-privacy/combiner/src/common/crypto-clients/crypto-client.ts deleted file mode 100644 index c57783a305..0000000000 --- a/packages/phone-number-privacy/combiner/src/common/crypto-clients/crypto-client.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { ErrorMessage, KeyVersionInfo } from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { performance } from 'perf_hooks' - -export interface ServicePartialSignature { - url: string - signature: string -} - -export abstract class CryptoClient { - protected unverifiedSignatures: ServicePartialSignature[] = [] - - constructor(protected readonly keyVersionInfo: KeyVersionInfo) {} - - /** - * Returns true if the number of valid signatures is enough to perform a combination - */ - public hasSufficientSignatures(): boolean { - return this.allSignaturesLength >= this.keyVersionInfo.threshold - } - - public addSignature(serviceResponse: ServicePartialSignature): void { - this.unverifiedSignatures.push(serviceResponse) - } - - /* - * Computes the signature for the blinded phone number using subclass-specific - * logic defined in _combineBlindedSignatureShares. - * Throws an exception if not enough valid signatures or on aggregation failure. - */ - public combineBlindedSignatureShares(blindedMessage: string, logger: Logger): string { - if (!this.hasSufficientSignatures()) { - const { threshold } = this.keyVersionInfo - logger.error( - { signatures: this.allSignaturesLength, required: threshold }, - ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES - ) - throw new Error( - `${ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES} ${this.allSignaturesLength}/${threshold}` - ) - } - - const start = `Start combineBlindedSignatureShares` - const end = `End combineBlindedSignatureShares` - performance.mark(start) - - const combinedSignature = this._combineBlindedSignatureShares(blindedMessage, logger) - - performance.mark(end) - performance.measure('combineBlindedSignatureShares', start, end) - - return combinedSignature - } - - /* - * Computes the signature for the blinded phone number. - * Must be implemented by subclass. - */ - protected abstract _combineBlindedSignatureShares(blindedMessage: string, logger: Logger): string - - /** - * Returns total number of signatures received; must be implemented by subclass. - */ - protected abstract get allSignaturesLength(): number -} diff --git a/packages/phone-number-privacy/combiner/src/common/crypto-clients/domain-crypto-client.ts b/packages/phone-number-privacy/combiner/src/common/crypto-clients/domain-crypto-client.ts deleted file mode 100644 index 22a1256d35..0000000000 --- a/packages/phone-number-privacy/combiner/src/common/crypto-clients/domain-crypto-client.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ErrorMessage, KeyVersionInfo, PoprfCombiner } from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { CryptoClient } from './crypto-client' - -export class DomainCryptoClient extends CryptoClient { - private poprfCombiner: PoprfCombiner - - constructor(protected readonly keyVersionInfo: KeyVersionInfo) { - super(keyVersionInfo) - this.poprfCombiner = new PoprfCombiner(keyVersionInfo.threshold) - } - - protected get allSignaturesLength(): number { - // No way of verifying signatures on the server-side - return this.unverifiedSignatures.length - } - - private get allSigsAsArray(): Uint8Array[] { - return this.unverifiedSignatures.map((response) => Buffer.from(response.signature, 'base64')) - } - - /* - * Aggregates blind partial signatures into a blind aggregated POPRF evaluation. - * On error, logs and throws exception for not enough signatures. - * Verification of partial signatures is not possible server-side - * (i.e. without the client's blinding factor). - */ - protected _combineBlindedSignatureShares(_blindedMessage: string, logger: Logger): string { - try { - const result = this.poprfCombiner.blindAggregate(this.allSigsAsArray) - if (result !== undefined) { - return result.toString('base64') - } - } catch (error) { - logger.error(ErrorMessage.SIGNATURE_AGGREGATION_FAILURE) - logger.error(error) - } - throw new Error(ErrorMessage.SIGNATURE_AGGREGATION_FAILURE) - } -} diff --git a/packages/phone-number-privacy/combiner/src/common/error.ts b/packages/phone-number-privacy/combiner/src/common/error.ts deleted file mode 100644 index 91060f2369..0000000000 --- a/packages/phone-number-privacy/combiner/src/common/error.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ErrorType } from '@celo/phone-number-privacy-common' - -export class OdisError extends Error { - constructor(readonly code: ErrorType, readonly parent?: Error, readonly status: number = 500) { - // This is necessary when extending Error Classes - super(code) // 'Error' breaks prototype chain here - Object.setPrototypeOf(this, new.target.prototype) // restore prototype chain - } -} - -export function wrapError( - valueOrError: Promise, - code: ErrorType, - status: number = 500 -): Promise { - return valueOrError.catch((parentErr) => { - throw new OdisError(code, parentErr, status) - }) -} diff --git a/packages/phone-number-privacy/combiner/src/common/handlers.ts b/packages/phone-number-privacy/combiner/src/common/handlers.ts deleted file mode 100644 index a8323540fc..0000000000 --- a/packages/phone-number-privacy/combiner/src/common/handlers.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { - ErrorMessage, - ErrorType, - OdisRequest, - OdisResponse, - PnpQuotaStatus, - send, - // tslint:disable-next-line: ordered-imports - SequentialDelayDomainState, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import opentelemetry, { SpanStatusCode } from '@opentelemetry/api' -import { SemanticAttributes } from '@opentelemetry/semantic-conventions' -import Logger from 'bunyan' -import { Request, Response } from 'express' -import { performance, PerformanceObserver } from 'perf_hooks' -import { getCombinerVersion } from '../config' -import { OdisError } from './error' - -const tracer = opentelemetry.trace.getTracer('combiner-tracer') - -export interface Locals { - logger: Logger -} - -export type PromiseHandler = ( - request: Request<{}, {}, R>, - res: Response, Locals> -) => Promise - -export function catchErrorHandler( - handler: PromiseHandler -): PromiseHandler { - return async (req, res) => { - try { - await handler(req, res) - } catch (err) { - const logger: Logger = res.locals.logger - logger.error(ErrorMessage.CAUGHT_ERROR_IN_ENDPOINT_HANDLER) - logger.error(err) - if (!res.headersSent) { - if (err instanceof OdisError) { - sendFailure(err.code, err.status, res, req.url) - } else { - sendFailure(ErrorMessage.UNKNOWN_ERROR, 500, res, req.url) - } - } else { - logger.error(ErrorMessage.ERROR_AFTER_RESPONSE_SENT) - } - } - } -} - -export function tracingHandler( - handler: PromiseHandler -): PromiseHandler { - return async (req, res) => { - return tracer.startActiveSpan( - req.url, - { - attributes: { - [SemanticAttributes.HTTP_ROUTE]: req.path, - [SemanticAttributes.HTTP_METHOD]: req.method, - [SemanticAttributes.HTTP_CLIENT_IP]: req.ip, - }, - }, - async (span) => { - try { - await handler(req, res) - span.setStatus({ - code: SpanStatusCode.OK, - }) - } catch (err: any) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: err instanceof Error ? err.message : 'Fail', - }) - throw err - } finally { - span.end() - } - } - ) - } -} - -export function meteringHandler( - handler: PromiseHandler -): PromiseHandler { - return async (req, res) => { - const logger: Logger = res.locals.logger - - // used for log based metrics - logger.info({ req: req.body }, 'Request received') - - const eventLoopLagMeasurementStart = Date.now() - setTimeout(() => { - const eventLoopLag = Date.now() - eventLoopLagMeasurementStart - logger.info({ eventLoopLag }, 'Measure event loop lag') - }) - const startMark = `Begin ${req.url}` - const endMark = `End ${req.url}` - const entryName = `${req.url} latency` - - const obs = new PerformanceObserver((list) => { - const entry = list.getEntriesByName(entryName)[0] - if (entry) { - logger.info({ latency: entry }, 'e2e response latency measured') - } - }) - obs.observe({ entryTypes: ['measure'], buffered: false }) - - performance.mark(startMark) - - try { - await handler(req, res) - if (res.headersSent) { - // used for log based metrics - logger.info({ res }, 'Response sent') - } - } finally { - performance.mark(endMark) - performance.measure(entryName, startMark, endMark) - performance.clearMarks() - obs.disconnect() - } - } -} - -export function timeoutHandler( - timeoutMs: number, - handler: PromiseHandler -): PromiseHandler { - return async (req, res) => { - const timeoutSignal = (AbortSignal as any).timeout(timeoutMs) - timeoutSignal.addEventListener( - 'abort', - () => { - if (!res.headersSent) { - sendFailure(ErrorMessage.TIMEOUT_FROM_SIGNER, 500, res, req.url) - } - }, - { once: true } - ) - - await handler(req, res) - } -} - -export async function disabledHandler( - req: Request<{}, {}, R>, - response: Response, Locals> -): Promise { - sendFailure(WarningMessage.API_UNAVAILABLE, 503, response, req.url) -} - -export function sendFailure( - error: ErrorType, - status: number, - response: Response, - _endpoint: string, - body?: Record // TODO remove any -) { - send( - response, - { - success: false, - version: getCombinerVersion(), - error, - ...body, - }, - status, - response.locals.logger - ) -} - -export interface Result { - status: number - body: OdisResponse -} - -export type ResultHandler = ( - request: Request<{}, {}, R>, - res: Response, Locals> -) => Promise> - -export function resultHandler( - resHandler: ResultHandler -): PromiseHandler { - return async (req, res) => { - const result = await resHandler(req, res) - send(res, result.body, result.status, res.locals.logger) - } -} - -export function errorResult( - status: number, - error: string, - quotaStatus?: PnpQuotaStatus | { status: SequentialDelayDomainState } -): Result { - // TODO remove any - return { - status, - body: { - success: false, - version: getCombinerVersion(), - error, - ...quotaStatus, - }, - } -} diff --git a/packages/phone-number-privacy/combiner/src/common/io.ts b/packages/phone-number-privacy/combiner/src/common/io.ts deleted file mode 100644 index 58aafaca4c..0000000000 --- a/packages/phone-number-privacy/combiner/src/common/io.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { - getRequestKeyVersion, - KEY_VERSION_HEADER, - KeyVersionInfo, - OdisRequest, - OdisResponse, - requestHasValidKeyVersion, - SignerEndpoint, -} from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Request } from 'express' -import * as http from 'http' -import * as https from 'https' -import fetch, { Response as FetchResponse } from 'node-fetch' -import { performance } from 'perf_hooks' -import { OdisConfig } from '../config' -import { isAbortError, Signer } from './combine' - -const httpAgent = new http.Agent({ keepAlive: true }) -const httpsAgent = new https.Agent({ keepAlive: true }) - -// tslint:disable-next-line: interface-over-type-literal -export type SignerResponse = { - url: string - res: OdisResponse -} - -export function requestHasSupportedKeyVersion( - request: Request<{}, {}, OdisRequest>, - config: OdisConfig, - logger: Logger -): boolean { - try { - getKeyVersionInfo(request, config, logger) - return true - } catch (err) { - logger.debug('Error caught in requestHasSupportedKeyVersion') - logger.debug(err) - return false - } -} - -export function getKeyVersionInfo( - request: Request<{}, {}, OdisRequest>, - config: OdisConfig, - logger: Logger -): KeyVersionInfo { - // If an invalid key version is present, we don't want this function to throw but - // to instead replace the key version with the default - // If a valid but unsupported key version is present, we want this function to throw - let requestKeyVersion: number | undefined - if (requestHasValidKeyVersion(request, logger)) { - requestKeyVersion = getRequestKeyVersion(request, logger) - } - const keyVersion = requestKeyVersion ?? config.keys.currentVersion - const supportedVersions: KeyVersionInfo[] = JSON.parse(config.keys.versions) // TODO add io-ts checks for this and signer array - const filteredSupportedVersions: KeyVersionInfo[] = supportedVersions.filter( - (v) => v.keyVersion === keyVersion - ) - if (!filteredSupportedVersions.length) { - throw new Error(`key version ${keyVersion} not supported`) - } - return filteredSupportedVersions[0] -} - -export async function fetchSignerResponseWithFallback( - signer: Signer, - signerEndpoint: SignerEndpoint, - keyVersion: number, - request: Request<{}, {}, R>, - logger: Logger, - abortSignal: AbortSignal -): Promise { - async function fetchSignerResponse(url: string): Promise { - // prettier-ignore - return fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - // Pnp requests provide authorization in the request header - ...(request.headers.authorization ? { Authorization: request.headers.authorization } : {}), - // Forward requested keyVersion if provided by client, otherwise use default keyVersion. - // This will be ignored for non-signing requests. - [KEY_VERSION_HEADER]: keyVersion.toString() - }, - body: JSON.stringify(request.body), - // @ts-expect-error -- wants a param for abortifThrown but thats not available on the incoming yet. @alec will fix it ;) - signal: abortSignal, - agent: url.startsWith("https://") ? httpsAgent : httpAgent - }) - } - - return measureTime(signer.url + signerEndpoint, () => - fetchSignerResponse(signer.url + signerEndpoint).catch((err) => { - logger.error({ url: signer.url, error: err }, `Signer failed with primary url`) - if (signer.fallbackUrl && !isAbortError(err)) { - logger.warn({ signer }, `Using fallback url to call signer`) - return fetchSignerResponse(signer.fallbackUrl + signerEndpoint) - } else { - throw err - } - }) - ) -} -async function measureTime(name: string, fn: () => Promise): Promise { - const start = `Start ${name}` - const end = `End ${name}` - performance.mark(start) - try { - const res = await fn() - return res - } finally { - performance.mark(end) - performance.measure(name, start, end) - } -} diff --git a/packages/phone-number-privacy/combiner/src/config.ts b/packages/phone-number-privacy/combiner/src/config.ts deleted file mode 100644 index 2097dbfb5e..0000000000 --- a/packages/phone-number-privacy/combiner/src/config.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { - BlockchainConfig, - FULL_NODE_TIMEOUT_IN_MS, - RETRY_COUNT, - RETRY_DELAY_IN_MS, - rootLogger, - TestUtils, - toBool, -} from '@celo/phone-number-privacy-common' -import * as functions from 'firebase-functions' -export function getCombinerVersion(): string { - return process.env.npm_package_version ?? require('../package.json').version ?? '0.0.0' -} -export const DEV_MODE = - process.env.NODE_ENV !== 'production' || process.env.FUNCTIONS_EMULATOR === 'true' - -export const FORNO_ALFAJORES = 'https://alfajores-forno.celo-testnet.org' - -// combiner always thinks these accounts/phoneNumbersa are verified to enable e2e testing -export const E2E_TEST_PHONE_NUMBERS_RAW: string[] = ['+14155550123', '+15555555555', '+14444444444'] - -export const E2E_TEST_ACCOUNTS: string[] = ['0x1be31a94361a391bbafb2a4ccd704f57dc04d4bb'] - -export const MAX_BLOCK_DISCREPANCY_THRESHOLD = 3 -export const MAX_TOTAL_QUOTA_DISCREPANCY_THRESHOLD = 5 -export const MAX_QUERY_COUNT_DISCREPANCY_THRESHOLD = 5 - -export interface OdisConfig { - serviceName: string - enabled: boolean - odisServices: { - signers: string - timeoutMilliSeconds: number - } - keys: { - currentVersion: number - versions: string // parse as KeyVersionInfo[] - } - fullNodeTimeoutMs: number - fullNodeRetryCount: number - fullNodeRetryDelayMs: number - shouldAuthenticate: boolean -} - -export interface CombinerConfig { - serviceName: string - blockchain: BlockchainConfig - phoneNumberPrivacy: OdisConfig - domains: OdisConfig -} - -let config: CombinerConfig - -const defaultServiceName = 'odis-combiner' - -if (DEV_MODE) { - rootLogger(defaultServiceName).debug('Running in dev mode') - const devSignersString = JSON.stringify([ - { - url: 'http://localhost:3001', - fallbackUrl: 'http://localhost:3001/fallback', - }, - { - url: 'http://localhost:3002', - fallbackUrl: 'http://localhost:3002/fallback', - }, - { - url: 'http://localhost:3003', - fallbackUrl: 'http://localhost:3003/fallback', - }, - ]) - config = { - serviceName: defaultServiceName, - blockchain: { - provider: FORNO_ALFAJORES, - }, - phoneNumberPrivacy: { - serviceName: defaultServiceName, - enabled: true, - odisServices: { - signers: devSignersString, - timeoutMilliSeconds: 5 * 1000, - }, - keys: { - currentVersion: 1, - versions: JSON.stringify([ - { - keyVersion: 1, - threshold: 2, - polynomial: TestUtils.Values.PNP_THRESHOLD_DEV_POLYNOMIAL_V1, - pubKey: TestUtils.Values.PNP_THRESHOLD_DEV_PUBKEY_V1, - }, - { - keyVersion: 2, - threshold: 2, - polynomial: TestUtils.Values.PNP_THRESHOLD_DEV_POLYNOMIAL_V2, - pubKey: TestUtils.Values.PNP_THRESHOLD_DEV_PUBKEY_V2, - }, - { - keyVersion: 3, - threshold: 2, - polynomial: TestUtils.Values.PNP_THRESHOLD_DEV_POLYNOMIAL_V3, - pubKey: TestUtils.Values.PNP_THRESHOLD_DEV_PUBKEY_V3, - }, - ]), - }, - fullNodeTimeoutMs: FULL_NODE_TIMEOUT_IN_MS, - fullNodeRetryCount: RETRY_COUNT, - fullNodeRetryDelayMs: RETRY_DELAY_IN_MS, - shouldAuthenticate: true, - }, - domains: { - serviceName: defaultServiceName, - enabled: true, - odisServices: { - signers: devSignersString, - timeoutMilliSeconds: 5 * 1000, - }, - keys: { - currentVersion: 1, - versions: JSON.stringify([ - { - keyVersion: 1, - threshold: 2, - polynomial: TestUtils.Values.DOMAINS_THRESHOLD_DEV_POLYNOMIAL_V1, - pubKey: TestUtils.Values.DOMAINS_THRESHOLD_DEV_PUBKEY_V1, - }, - { - keyVersion: 2, - threshold: 2, - polynomial: TestUtils.Values.DOMAINS_THRESHOLD_DEV_POLYNOMIAL_V2, - pubKey: TestUtils.Values.DOMAINS_THRESHOLD_DEV_PUBKEY_V2, - }, - { - keyVersion: 3, - threshold: 2, - polynomial: TestUtils.Values.DOMAINS_THRESHOLD_DEV_POLYNOMIAL_V3, - pubKey: TestUtils.Values.DOMAINS_THRESHOLD_DEV_PUBKEY_V3, - }, - ]), - }, - fullNodeTimeoutMs: FULL_NODE_TIMEOUT_IN_MS, - fullNodeRetryCount: RETRY_COUNT, - fullNodeRetryDelayMs: RETRY_DELAY_IN_MS, - shouldAuthenticate: true, - }, - } -} else { - const functionConfig = functions.config() - config = { - serviceName: functionConfig.service.name ?? defaultServiceName, - blockchain: { - provider: functionConfig.blockchain.provider, - apiKey: functionConfig.blockchain.api_key, - }, - phoneNumberPrivacy: { - serviceName: functionConfig.pnp.service_name ?? defaultServiceName, - enabled: toBool(functionConfig.pnp.enabled, false), - odisServices: { - signers: functionConfig.pnp.odisservices, - timeoutMilliSeconds: functionConfig.pnp.timeout_ms - ? Number(functionConfig.pnp.timeout_ms) - : 5 * 1000, - }, - keys: { - currentVersion: Number(functionConfig.pnp_keys.current_version), - versions: functionConfig.pnp_keys.versions, - }, - fullNodeTimeoutMs: Number(functionConfig.pnp.full_node_timeout_ms ?? FULL_NODE_TIMEOUT_IN_MS), - fullNodeRetryCount: Number(functionConfig.pnp.full_node_retry_count ?? RETRY_COUNT), - fullNodeRetryDelayMs: Number( - functionConfig.pnp.full_node_retry_delay_ms ?? RETRY_DELAY_IN_MS - ), - shouldAuthenticate: toBool(functionConfig.pnp.should_authenticate, true), - }, - domains: { - serviceName: functionConfig.domains.service_name ?? defaultServiceName, - enabled: toBool(functionConfig.domains.enabled, false), - odisServices: { - signers: functionConfig.domains.odisservices, - timeoutMilliSeconds: functionConfig.domains.timeout_ms - ? Number(functionConfig.domains.timeout_ms) - : 5 * 1000, - }, - keys: { - currentVersion: Number(functionConfig.domains_keys.current_version), - versions: functionConfig.domains_keys.versions, - }, - fullNodeTimeoutMs: Number(functionConfig.pnp.full_node_timeout_ms ?? FULL_NODE_TIMEOUT_IN_MS), // TODO refactor config - domains endpoints don't use full node - fullNodeRetryCount: Number(functionConfig.pnp.full_node_retry_count ?? RETRY_COUNT), - fullNodeRetryDelayMs: Number( - functionConfig.pnp.full_node_retry_delay_ms ?? RETRY_DELAY_IN_MS - ), - shouldAuthenticate: true, - }, - } -} -export default config diff --git a/packages/phone-number-privacy/combiner/src/domain/endpoints/disable/action.ts b/packages/phone-number-privacy/combiner/src/domain/endpoints/disable/action.ts deleted file mode 100644 index 2de518eba5..0000000000 --- a/packages/phone-number-privacy/combiner/src/domain/endpoints/disable/action.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - CombinerEndpoint, - DisableDomainRequest, - disableDomainRequestSchema, - disableDomainResponseSchema, - DomainSchema, - ErrorMessage, - getSignerEndpoint, - SequentialDelayDomainStateSchema, - verifyDisableDomainRequestAuthenticity, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { Signer, thresholdCallToSigners } from '../../../common/combine' -import { errorResult, ResultHandler } from '../../../common/handlers' -import { getKeyVersionInfo } from '../../../common/io' -import { getCombinerVersion, OdisConfig } from '../../../config' -import { logDomainResponseDiscrepancies } from '../../services/log-responses' -import { findThresholdDomainState } from '../../services/threshold-state' - -export function disableDomain( - signers: Signer[], - config: OdisConfig -): ResultHandler { - return async (request, response) => { - if (!disableDomainRequestSchema(DomainSchema).is(request.body)) { - return errorResult(400, WarningMessage.INVALID_INPUT) - } - - if (!verifyDisableDomainRequestAuthenticity(request.body)) { - return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) - } - - // TODO remove? - const keyVersionInfo = getKeyVersionInfo(request, config, response.locals.logger) - - const { signerResponses, maxErrorCode } = await thresholdCallToSigners( - response.locals.logger, - { - signers, - endpoint: getSignerEndpoint(CombinerEndpoint.DISABLE_DOMAIN), - request, - keyVersionInfo, - requestTimeoutMS: config.odisServices.timeoutMilliSeconds, - responseSchema: disableDomainResponseSchema(SequentialDelayDomainStateSchema), - shouldCheckKeyVersion: false, - } - ) - - logDomainResponseDiscrepancies(response.locals.logger, signerResponses) - try { - const disableDomainStatus = findThresholdDomainState( - keyVersionInfo, - signerResponses, - signers.length - ) - if (disableDomainStatus.disabled) { - return { - status: 200, - body: { - success: true, - version: getCombinerVersion(), - status: disableDomainStatus, - }, - } - } - } catch (err) { - response.locals.logger.error( - { err }, - 'Error combining signer disable domain status responses' - ) - } - - return errorResult(maxErrorCode ?? 500, ErrorMessage.THRESHOLD_DISABLE_DOMAIN_FAILURE) - } -} diff --git a/packages/phone-number-privacy/combiner/src/domain/endpoints/quota/action.ts b/packages/phone-number-privacy/combiner/src/domain/endpoints/quota/action.ts deleted file mode 100644 index 8112f598a2..0000000000 --- a/packages/phone-number-privacy/combiner/src/domain/endpoints/quota/action.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - CombinerEndpoint, - DomainQuotaStatusRequest, - domainQuotaStatusRequestSchema, - domainQuotaStatusResponseSchema, - DomainSchema, - ErrorMessage, - getSignerEndpoint, - SequentialDelayDomainStateSchema, - verifyDomainQuotaStatusRequestAuthenticity, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { Signer, thresholdCallToSigners } from '../../../common/combine' -import { errorResult, ResultHandler } from '../../../common/handlers' -import { getKeyVersionInfo } from '../../../common/io' -import { getCombinerVersion, OdisConfig } from '../../../config' -import { logDomainResponseDiscrepancies } from '../../services/log-responses' -import { findThresholdDomainState } from '../../services/threshold-state' - -export function domainQuota( - signers: Signer[], - config: OdisConfig -): ResultHandler { - return async (request, response) => { - if (!domainQuotaStatusRequestSchema(DomainSchema).is(request.body)) { - return errorResult(400, WarningMessage.INVALID_INPUT) - } - - if (!verifyDomainQuotaStatusRequestAuthenticity(request.body)) { - return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) - } - - // TODO remove? - const keyVersionInfo = getKeyVersionInfo(request, config, response.locals.logger) - - const { signerResponses, maxErrorCode } = await thresholdCallToSigners(response.locals.logger, { - signers, - endpoint: getSignerEndpoint(CombinerEndpoint.DOMAIN_QUOTA_STATUS), - request, - keyVersionInfo, - requestTimeoutMS: config.odisServices.timeoutMilliSeconds, - responseSchema: domainQuotaStatusResponseSchema(SequentialDelayDomainStateSchema), - shouldCheckKeyVersion: false, - }) - - logDomainResponseDiscrepancies(response.locals.logger, signerResponses) - if (signerResponses.length >= keyVersionInfo.threshold) { - try { - return { - status: 200, - body: { - success: true, - version: getCombinerVersion(), - status: findThresholdDomainState(keyVersionInfo, signerResponses, signers.length), - }, - } - } catch (err) { - response.locals.logger.error(err, 'Error combining signer quota status responses') - } - } - return errorResult(maxErrorCode ?? 500, ErrorMessage.THRESHOLD_DOMAIN_QUOTA_STATUS_FAILURE) - } -} diff --git a/packages/phone-number-privacy/combiner/src/domain/endpoints/sign/action.ts b/packages/phone-number-privacy/combiner/src/domain/endpoints/sign/action.ts deleted file mode 100644 index d696c2f4a0..0000000000 --- a/packages/phone-number-privacy/combiner/src/domain/endpoints/sign/action.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { - CombinerEndpoint, - DomainRestrictedSignatureRequest, - domainRestrictedSignatureRequestSchema, - domainRestrictedSignatureResponseSchema, - DomainSchema, - ErrorMessage, - ErrorType, - getSignerEndpoint, - OdisResponse, - SequentialDelayDomainStateSchema, - verifyDomainRestrictedSignatureRequestAuthenticity, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import assert from 'node:assert' -import { Signer, thresholdCallToSigners } from '../../../common/combine' -import { DomainCryptoClient } from '../../../common/crypto-clients/domain-crypto-client' -import { errorResult, ResultHandler } from '../../../common/handlers' -import { getKeyVersionInfo, requestHasSupportedKeyVersion } from '../../../common/io' -import { getCombinerVersion, OdisConfig } from '../../../config' -import { logDomainResponseDiscrepancies } from '../../services/log-responses' -import { findThresholdDomainState } from '../../services/threshold-state' - -export function domainSign( - signers: Signer[], - config: OdisConfig -): ResultHandler { - return async (request, response) => { - const { logger } = response.locals - - if (!domainRestrictedSignatureRequestSchema(DomainSchema).is(request.body)) { - return errorResult(400, WarningMessage.INVALID_INPUT) - } - if (!requestHasSupportedKeyVersion(request, config, logger)) { - return errorResult(400, WarningMessage.INVALID_KEY_VERSION_REQUEST) - } - - // Note that signing requests may include a nonce for replay protection that will be checked by - // the signer, but is not checked here. As a result, requests that pass the authentication check - // here may still fail when sent to the signer. - if (!verifyDomainRestrictedSignatureRequestAuthenticity(request.body)) { - return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) - } - - const keyVersionInfo = getKeyVersionInfo(request, config, logger) - const crypto = new DomainCryptoClient(keyVersionInfo) - - const processResult = async ( - res: OdisResponse - ): Promise => { - assert(res.success) - // TODO remove the need to pass url here - crypto.addSignature({ url: request.url, signature: res.signature }) - - // Send response immediately once we cross threshold - // BLS threshold signatures can be combined without all partial signatures - if (crypto.hasSufficientSignatures()) { - try { - crypto.combineBlindedSignatureShares(request.body.blindedMessage, logger) - // Close outstanding requests - return true - } catch (err) { - // One or more signatures failed verification and were discarded. - logger.info('Error caught in receiveSuccess') - logger.info(err) - // Continue to collect signatures. - } - } - return false - } - - const { signerResponses, maxErrorCode } = await thresholdCallToSigners( - response.locals.logger, - { - signers, - endpoint: getSignerEndpoint(CombinerEndpoint.DOMAIN_SIGN), - request, - keyVersionInfo, - requestTimeoutMS: config.odisServices.timeoutMilliSeconds, - responseSchema: domainRestrictedSignatureResponseSchema(SequentialDelayDomainStateSchema), - shouldCheckKeyVersion: true, - }, - processResult - ) - - logDomainResponseDiscrepancies(response.locals.logger, signerResponses) - - if (crypto.hasSufficientSignatures()) { - try { - const combinedSignature = crypto.combineBlindedSignatureShares( - request.body.blindedMessage, - logger - ) - - return { - status: 200, - body: { - success: true, - version: getCombinerVersion(), - signature: combinedSignature, - status: findThresholdDomainState(keyVersionInfo, signerResponses, signers.length), - }, - } - } catch (err) { - // May fail upon combining signatures if too many sigs are invalid - logger.error('Combining signatures failed in combine') - logger.error(err) - // Fallback to handleMissingSignatures - } - } - - const errorCode = maxErrorCode ?? 500 - const error = errorCodeToError(errorCode) - return errorResult(errorCode, error) - } -} - -function errorCodeToError(errorCode: number): ErrorType { - switch (errorCode) { - case 429: - return WarningMessage.EXCEEDED_QUOTA - case 401: - // Authentication is checked in the combiner, but invalid nonces are passed through - return WarningMessage.INVALID_NONCE - default: - return ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES - } -} diff --git a/packages/phone-number-privacy/combiner/src/domain/services/log-responses.ts b/packages/phone-number-privacy/combiner/src/domain/services/log-responses.ts deleted file mode 100644 index 7f4b3bebb1..0000000000 --- a/packages/phone-number-privacy/combiner/src/domain/services/log-responses.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { DomainRequest, WarningMessage } from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { SignerResponse } from '../../common/io' - -export function logDomainResponseDiscrepancies( - logger: Logger, - responses: Array> -) { - const parsedResponses: Array<{ - signerUrl: string - values: { - version: string - counter: number - disabled: boolean - timer: number - } - }> = [] - responses.forEach((response) => { - if (response.res.success) { - const { version, status } = response.res - parsedResponses.push({ - signerUrl: response.url, - values: { - version, - counter: status.counter, - disabled: status.disabled, - timer: status.timer, - }, - }) - } - }) - if (parsedResponses.length === 0) { - logger.warn('No successful signer responses found!') - return - } - - // log all responses if we notice any discrepancies to aid with debugging - const first = JSON.stringify(parsedResponses[0].values) - for (let i = 1; i < parsedResponses.length; i++) { - if (JSON.stringify(parsedResponses[i].values) !== first) { - logger.warn({ parsedResponses }, WarningMessage.SIGNER_RESPONSE_DISCREPANCIES) - break - } - } - - // disabled - const numDisabled = parsedResponses.filter((res) => res.values.disabled).length - if (numDisabled > 0 && numDisabled < parsedResponses.length) { - logger.error({ parsedResponses }, WarningMessage.INCONSISTENT_SIGNER_DOMAIN_DISABLED_STATES) - } -} diff --git a/packages/phone-number-privacy/combiner/src/domain/services/threshold-state.ts b/packages/phone-number-privacy/combiner/src/domain/services/threshold-state.ts deleted file mode 100644 index 31b8a30326..0000000000 --- a/packages/phone-number-privacy/combiner/src/domain/services/threshold-state.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { DomainRequest, DomainState, KeyVersionInfo } from '@celo/phone-number-privacy-common' -import { SignerResponse } from '../../common/io' - -export function findThresholdDomainState( - keyVersionInfo: KeyVersionInfo, - rawSignerResponses: Array>, - totalSigners: number -): DomainState { - const { threshold } = keyVersionInfo - // Get the domain status from the responses, filtering out responses that don't have the status. - const domainStates = rawSignerResponses - .map((signerResponse) => ('status' in signerResponse.res ? signerResponse.res.status : null)) - .filter((state: DomainState | null | undefined): state is DomainState => !!state) - - // Note: when the threshold > # total signers - threshold, it's possible that we - // throw an error here when the domain is disabled. While the domain is technically disabled, - // the hope is to increase the "safety margin" of the number of signers that have - // also disabled this domain.This can be changed in the future (if we think that - // the safety margin is no longer needed) by simply checking if the domain is disabled - // before checking if the threshold of enabled responses has been met. - if (domainStates.length < threshold) { - throw new Error('Insufficient number of signer responses') - } - - // Check whether the domain is disabled, either by all signers or by some. - const domainStatesEnabled = domainStates.filter((ds) => !ds.disabled) - const numDisabled = domainStates.length - domainStatesEnabled.length - - if (totalSigners - numDisabled < threshold) { - return { timer: 0, counter: 0, disabled: true, now: 0 } - } - - // Ideally users will resubmit the request in this case. - if (domainStatesEnabled.length < threshold) { - throw new Error('Insufficient number of signer responses. Domain may be disabled') - } - - // Set n to last signer index in a quorum of signers are sorted from least to most restrictive. - const n = threshold - 1 - - const domainStatesAscendingByCounter = domainStatesEnabled.sort((a, b) => a.counter - b.counter) - const nthLeastRestrictiveByCounter = domainStatesAscendingByCounter[n] - const thresholdCounter = nthLeastRestrictiveByCounter.counter - - // Client should submit requests with nonce === thresholdCounter - - const domainStatesWithThresholdCounter = domainStatesEnabled.filter( - (ds) => ds.counter <= thresholdCounter - ) - - const domainStatesAscendingByTimestampRestrictiveness = domainStatesWithThresholdCounter.sort( - (a, b) => a.timer - a.now - (b.timer - b.now) - /** - * Please see '@celo/phone-number-privacy-common/src/domains/sequential-delay.ts' - * and https://github.com/celo-org/celo-proposals/blob/master/CIPs/CIP-0040/sequentialDelayDomain.md - * - * For a given DomainState, it is always the case that 'now' >= 'timer'. This ordering ensures - * that we take the 'timer' and 'date' from the same DomainState while still returning a reasonable - * definition of the "nth least restrictive" values. For simplicity, we do not take into consideration - * the 'delay' until the next request will be accepted as that would require calculating this value for - * each DomainState with the checkSequentialDelayDomainState algorithm in sequential-delay.ts. - * This would add complexity because DomainStates may have different values for 'counter' that dramatically - * alter this 'delay' and we want to protect the user's quota by returning the lowest possible - * threshold 'counter'. Feel free to implement a more exact solution if you're up for a coding challenge :) - */ - ) - const nthLeastRestrictiveByTimestamps = domainStatesAscendingByTimestampRestrictiveness[n] - - return { - timer: nthLeastRestrictiveByTimestamps.timer, - counter: thresholdCounter, - disabled: false, - now: nthLeastRestrictiveByTimestamps.now, - } -} diff --git a/packages/phone-number-privacy/combiner/src/index.ts b/packages/phone-number-privacy/combiner/src/index.ts deleted file mode 100644 index 02297508a8..0000000000 --- a/packages/phone-number-privacy/combiner/src/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as functions from 'firebase-functions' -import config from './config' -import { startCombiner } from './server' - -require('dotenv').config() - -export const combiner = functions - .region('us-central1') - .runWith({ - // Keep instances warm for mainnet functions - // Defined check required for running tests vs. deployment - minInstances: functions.config().service ? Number(functions.config().service.min_instances) : 0, - memory: functions.config().service ? functions.config().service.memory : '512MB', - }) - .https.onRequest(startCombiner(config)) - -export * from './config' diff --git a/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/action.ts b/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/action.ts deleted file mode 100644 index 3f06d1a87b..0000000000 --- a/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/action.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - authenticateUser, - CombinerEndpoint, - DataEncryptionKeyFetcher, - ErrorMessage, - getSignerEndpoint, - hasValidAccountParam, - isBodyReasonablySized, - PnpQuotaRequest, - PnpQuotaRequestSchema, - PnpQuotaResponseSchema, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { Request } from 'express' -import { Signer, thresholdCallToSigners } from '../../../common/combine' -import { errorResult, ResultHandler } from '../../../common/handlers' -import { getKeyVersionInfo } from '../../../common/io' -import { getCombinerVersion, OdisConfig } from '../../../config' -import { logPnpSignerResponseDiscrepancies } from '../../services/log-responses' -import { findCombinerQuotaState } from '../../services/threshold-state' - -export function pnpQuota( - signers: Signer[], - config: OdisConfig, - dekFetcher: DataEncryptionKeyFetcher -): ResultHandler { - return async (request, response) => { - const logger = response.locals.logger - - if (!isValidRequest(request)) { - return errorResult(400, WarningMessage.INVALID_INPUT) - } - - if (config.shouldAuthenticate) { - if (!(await authenticateUser(request, logger, dekFetcher))) { - return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) - } - } - - // TODO remove this, we shouldn't need keyVersionInfo for non-signing endpoints - const keyVersionInfo = getKeyVersionInfo(request, config, logger) - - const { signerResponses, maxErrorCode } = await thresholdCallToSigners(logger, { - signers, - endpoint: getSignerEndpoint(CombinerEndpoint.PNP_QUOTA), - request, - keyVersionInfo, - requestTimeoutMS: config.odisServices.timeoutMilliSeconds, - responseSchema: PnpQuotaResponseSchema, - shouldCheckKeyVersion: false, - }) - const warnings = logPnpSignerResponseDiscrepancies(logger, signerResponses) - - const { threshold } = keyVersionInfo - - if (signerResponses.length >= threshold) { - try { - const quotaStatus = findCombinerQuotaState(keyVersionInfo, signerResponses, warnings) - return { - status: 200, - body: { - success: true, - version: getCombinerVersion(), - ...quotaStatus, - warnings, - }, - } - } catch (err) { - logger.error(err, 'Error combining signer quota status responses') - } - } - return errorResult(maxErrorCode ?? 500, ErrorMessage.THRESHOLD_PNP_QUOTA_STATUS_FAILURE) - } -} - -function isValidRequest( - request: Request<{}, {}, unknown> -): request is Request<{}, {}, PnpQuotaRequest> { - return ( - PnpQuotaRequestSchema.is(request.body) && - hasValidAccountParam(request.body) && - isBodyReasonablySized(request.body) - ) -} diff --git a/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/action.ts b/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/action.ts deleted file mode 100644 index 9109bfc286..0000000000 --- a/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/action.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { - authenticateUser, - CombinerEndpoint, - DataEncryptionKeyFetcher, - ErrorMessage, - ErrorType, - getSignerEndpoint, - hasValidAccountParam, - hasValidBlindedPhoneNumberParam, - isBodyReasonablySized, - OdisResponse, - SignMessageRequest, - SignMessageRequestSchema, - SignMessageResponseSchema, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { Request } from 'express' -import assert from 'node:assert' -import { Signer, thresholdCallToSigners } from '../../../common/combine' -import { BLSCryptographyClient } from '../../../common/crypto-clients/bls-crypto-client' -import { errorResult, ResultHandler } from '../../../common/handlers' -import { getKeyVersionInfo, requestHasSupportedKeyVersion } from '../../../common/io' -import { getCombinerVersion, OdisConfig } from '../../../config' -import { logPnpSignerResponseDiscrepancies } from '../../services/log-responses' -import { findCombinerQuotaState } from '../../services/threshold-state' - -export function pnpSign( - signers: Signer[], - config: OdisConfig, - dekFetcher: DataEncryptionKeyFetcher -): ResultHandler { - return async (request, response) => { - const logger = response.locals.logger - if (!isValidRequest(request)) { - return errorResult(400, WarningMessage.INVALID_INPUT) - } - - if (!requestHasSupportedKeyVersion(request, config, response.locals.logger)) { - return errorResult(400, WarningMessage.INVALID_KEY_VERSION_REQUEST) - } - - if (config.shouldAuthenticate) { - if (!(await authenticateUser(request, logger, dekFetcher))) { - return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) - } - } - - const keyVersionInfo = getKeyVersionInfo(request, config, logger) - const crypto = new BLSCryptographyClient(keyVersionInfo) - - const processResult = async (result: OdisResponse): Promise => { - assert(result.success) - crypto.addSignature({ url: request.url, signature: result.signature }) - - // Send response immediately once we cross threshold - // BLS threshold signatures can be combined without all partial signatures - if (crypto.hasSufficientSignatures()) { - try { - crypto.combineBlindedSignatureShares(request.body.blindedQueryPhoneNumber, logger) - // Close outstanding requests - return true - } catch (err) { - // One or more signatures failed verification and were discarded. - logger.info('Error caught in processRequest') - logger.info(err) - // Continue to collect signatures. - } - } - return false - } - - const { signerResponses, maxErrorCode } = await thresholdCallToSigners( - logger, - { - signers, - endpoint: getSignerEndpoint(CombinerEndpoint.PNP_SIGN), - request, - keyVersionInfo, - requestTimeoutMS: config.odisServices.timeoutMilliSeconds, - responseSchema: SignMessageResponseSchema, - shouldCheckKeyVersion: true, - }, - processResult - ) - - const warnings = logPnpSignerResponseDiscrepancies(logger, signerResponses) - - if (crypto.hasSufficientSignatures()) { - try { - const combinedSignature = crypto.combineBlindedSignatureShares( - request.body.blindedQueryPhoneNumber, - logger - ) - - return { - status: 200, - body: { - success: true, - version: getCombinerVersion(), - signature: combinedSignature, - ...findCombinerQuotaState(keyVersionInfo, signerResponses, warnings), - warnings, - }, - } - } catch (error) { - // May fail upon combining signatures if too many sigs are invalid - // Fallback to handleMissingSignatures - logger.error(error) - } - } - - const errorCode = maxErrorCode ?? 500 - const error = errorCodeToError(errorCode) - return errorResult(errorCode, error) - } -} - -function isValidRequest( - request: Request<{}, {}, unknown> -): request is Request<{}, {}, SignMessageRequest> { - return ( - SignMessageRequestSchema.is(request.body) && - hasValidAccountParam(request.body) && - hasValidBlindedPhoneNumberParam(request.body) && - isBodyReasonablySized(request.body) - ) -} - -function errorCodeToError(errorCode: number): ErrorType { - switch (errorCode) { - case 403: - return WarningMessage.EXCEEDED_QUOTA - default: - return ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES - } -} diff --git a/packages/phone-number-privacy/combiner/src/pnp/services/log-responses.ts b/packages/phone-number-privacy/combiner/src/pnp/services/log-responses.ts deleted file mode 100644 index 89bd98c268..0000000000 --- a/packages/phone-number-privacy/combiner/src/pnp/services/log-responses.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { - PnpQuotaRequest, - SignMessageRequest, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { SignerResponse } from '../../common/io' -import { - MAX_QUERY_COUNT_DISCREPANCY_THRESHOLD, - MAX_TOTAL_QUOTA_DISCREPANCY_THRESHOLD, -} from '../../config' - -export function logPnpSignerResponseDiscrepancies( - logger: Logger, - responses: Array> -): string[] { - const warnings: string[] = [] - - // TODO responses should all already be successes due to CombineAction receiveSuccess - // https://github.com/celo-org/celo-monorepo/issues/9826 - - const parsedResponses: Array<{ - signerUrl: string - values: { - version: string - performedQueryCount: number - totalQuota: number - warnings?: string[] - } - }> = [] - responses.forEach((response) => { - if (response.res.success) { - const { version, performedQueryCount, totalQuota, warnings: _warnings } = response.res - parsedResponses.push({ - signerUrl: response.url, - values: { version, performedQueryCount, totalQuota, warnings: _warnings }, - }) - } - }) - if (parsedResponses.length === 0) { - logger.warn('No successful signer responses found!') - return warnings - } - - // log all responses if we notice any discrepancies to aid with debugging - const first = JSON.stringify(parsedResponses[0].values) - for (let i = 1; i < parsedResponses.length; i++) { - if (JSON.stringify(parsedResponses[i].values) !== first) { - logger.warn({ parsedResponses }, WarningMessage.SIGNER_RESPONSE_DISCREPANCIES) - warnings.push(WarningMessage.SIGNER_RESPONSE_DISCREPANCIES) - break - } - } - - // totalQuota - const sortedByTotalQuota = parsedResponses.sort( - (a, b) => a.values.totalQuota - b.values.totalQuota - ) - if ( - sortedByTotalQuota[sortedByTotalQuota.length - 1].values.totalQuota - - sortedByTotalQuota[0].values.totalQuota >= - MAX_TOTAL_QUOTA_DISCREPANCY_THRESHOLD - ) { - logger.error({ sortedByTotalQuota }, WarningMessage.INCONSISTENT_SIGNER_QUOTA_MEASUREMENTS) - warnings.push(WarningMessage.INCONSISTENT_SIGNER_QUOTA_MEASUREMENTS) - } - - // performedQueryCount - const sortedByQueryCount = parsedResponses.sort( - (a, b) => a.values.performedQueryCount - b.values.performedQueryCount - ) - if ( - sortedByQueryCount[sortedByQueryCount.length - 1].values.performedQueryCount - - sortedByQueryCount[0].values.performedQueryCount >= - MAX_QUERY_COUNT_DISCREPANCY_THRESHOLD - ) { - logger.error({ sortedByQueryCount }, WarningMessage.INCONSISTENT_SIGNER_QUERY_MEASUREMENTS) - warnings.push(WarningMessage.INCONSISTENT_SIGNER_QUERY_MEASUREMENTS) - } - - return warnings -} diff --git a/packages/phone-number-privacy/combiner/src/pnp/services/threshold-state.ts b/packages/phone-number-privacy/combiner/src/pnp/services/threshold-state.ts deleted file mode 100644 index 739bc6c503..0000000000 --- a/packages/phone-number-privacy/combiner/src/pnp/services/threshold-state.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - KeyVersionInfo, - OdisRequest, - PnpQuotaStatus, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { SignerResponse } from '../../common/io' -import { MAX_TOTAL_QUOTA_DISCREPANCY_THRESHOLD } from '../../config' - -export function findCombinerQuotaState( - keyVersionInfo: KeyVersionInfo, - rawSignerResponses: Array>, - warnings: string[] -): PnpQuotaStatus { - const { threshold } = keyVersionInfo - const signerResponses = rawSignerResponses - .map((signerResponse) => signerResponse.res) - .filter((res) => res.success) as PnpQuotaStatus[] - const sortedResponses = signerResponses.sort( - (a, b) => b.totalQuota - b.performedQueryCount - (a.totalQuota - a.performedQueryCount) - ) - - const totalQuotaAvg = - sortedResponses.map((r) => r.totalQuota).reduce((a, b) => a + b) / sortedResponses.length - const totalQuotaStDev = Math.sqrt( - sortedResponses.map((r) => (r.totalQuota - totalQuotaAvg) ** 2).reduce((a, b) => a + b) / - sortedResponses.length - ) - if (totalQuotaStDev > MAX_TOTAL_QUOTA_DISCREPANCY_THRESHOLD) { - // TODO(2.0.0): add alerting for this - throw new Error(WarningMessage.INCONSISTENT_SIGNER_QUOTA_MEASUREMENTS) - } else if (totalQuotaStDev > 0) { - warnings.push( - WarningMessage.INCONSISTENT_SIGNER_QUOTA_MEASUREMENTS + - ', using threshold signer as best guess' - ) - } - - // TODO(2.0.0) currently this check is not needed, as checking for sufficient number of responses and - // filtering for successes is already done in the action. Consider adding back in based on the - // result of https://github.com/celo-org/celo-monorepo/issues/9826 - // if (signerResponses.length < threshold) { - // throw new Error('Insufficient number of successful signer responses') - // } - - const thresholdSigner = sortedResponses[threshold - 1] - return { - performedQueryCount: thresholdSigner.performedQueryCount, - totalQuota: thresholdSigner.totalQuota, - } -} diff --git a/packages/phone-number-privacy/combiner/src/server.ts b/packages/phone-number-privacy/combiner/src/server.ts deleted file mode 100644 index 94ca03ffcf..0000000000 --- a/packages/phone-number-privacy/combiner/src/server.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { ContractKit } from '@celo/contractkit' -import { - CombinerEndpoint, - getContractKitWithAgent, - KEY_VERSION_HEADER, - loggerMiddleware, - newContractKitFetcher, - OdisRequest, - rootLogger, -} from '@celo/phone-number-privacy-common' -import express, { RequestHandler } from 'express' -import { Signer } from './common/combine' -import { - catchErrorHandler, - disabledHandler, - Locals, - meteringHandler, - resultHandler, - ResultHandler, - tracingHandler, -} from './common/handlers' -import { CombinerConfig, getCombinerVersion } from './config' -import { disableDomain } from './domain/endpoints/disable/action' -import { domainQuota } from './domain/endpoints/quota/action' -import { domainSign } from './domain/endpoints/sign/action' -import { pnpQuota } from './pnp/endpoints/quota/action' -import { pnpSign } from './pnp/endpoints/sign/action' - -require('events').EventEmitter.defaultMaxListeners = 15 - -export function startCombiner(config: CombinerConfig, kit?: ContractKit) { - const logger = rootLogger(config.serviceName) - - kit = kit ?? getContractKitWithAgent(config.blockchain) - - logger.info('Creating combiner express server') - const app = express() - - // TODO get logger to show accurate serviceName - // (https://github.com/celo-org/celo-monorepo/issues/9809) - app.use(express.json({ limit: '0.2mb' }) as RequestHandler, loggerMiddleware(config.serviceName)) - - // Enable cross origin resource sharing from any domain so ODIS can be interacted with from web apps - // - // Security note: Allowing unrestricted cross-origin requests is acceptable here because any authenticated actions - // must include a signature in the request body. In particular, ODIS _does not_ use cookies to transmit authentication - // data. If ODIS is altered to use cookies for authentication data, this CORS policy should be reconsidered. - app.use((_, res, next) => { - res.header('Access-Control-Allow-Origin', '*') - res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') - res.header( - 'Access-Control-Allow-Headers', - `Origin, X-Requested-With, Content-Type, Accept, Authorization, ${KEY_VERSION_HEADER}` - ) - next() - }) - - app.get(CombinerEndpoint.STATUS, (_req, res) => { - res.status(200).json({ - version: getCombinerVersion(), - }) - }) - - const dekFetcher = newContractKitFetcher( - kit, - logger, - config.phoneNumberPrivacy.fullNodeTimeoutMs, - config.phoneNumberPrivacy.fullNodeRetryCount, - config.phoneNumberPrivacy.fullNodeRetryDelayMs - ) - - const pnpSigners: Signer[] = JSON.parse(config.phoneNumberPrivacy.odisServices.signers) - const domainSigners: Signer[] = JSON.parse(config.domains.odisServices.signers) - - const { domains, phoneNumberPrivacy } = config - - app.post( - CombinerEndpoint.PNP_QUOTA, - createHandler(phoneNumberPrivacy.enabled, pnpQuota(pnpSigners, phoneNumberPrivacy, dekFetcher)) - ) - app.post( - CombinerEndpoint.PNP_SIGN, - createHandler(phoneNumberPrivacy.enabled, pnpSign(pnpSigners, phoneNumberPrivacy, dekFetcher)) - ) - app.post( - CombinerEndpoint.DOMAIN_QUOTA_STATUS, - createHandler(domains.enabled, domainQuota(domainSigners, domains)) - ) - app.post( - CombinerEndpoint.DOMAIN_SIGN, - createHandler(domains.enabled, domainSign(domainSigners, domains)) - ) - app.post( - CombinerEndpoint.DISABLE_DOMAIN, - createHandler(domains.enabled, disableDomain(domainSigners, domains)) - ) - - return app -} - -function createHandler( - enabled: boolean, - action: ResultHandler -): RequestHandler<{}, {}, R, {}, Locals> { - return catchErrorHandler( - tracingHandler(meteringHandler(enabled ? resultHandler(action) : disabledHandler)) - ) -} diff --git a/packages/phone-number-privacy/combiner/src/tracing.ts b/packages/phone-number-privacy/combiner/src/tracing.ts deleted file mode 100644 index 3d3f699da3..0000000000 --- a/packages/phone-number-privacy/combiner/src/tracing.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { JaegerExporter } from '@opentelemetry/exporter-jaeger' -import { registerInstrumentations } from '@opentelemetry/instrumentation' -import { Resource } from '@opentelemetry/resources' -import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base' -import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node' -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' - -import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node' - -const options = { - tags: [], - endpoint: process.env.TRACER_ENDPOINT, -} - -// Optionally register instrumentation libraries -registerInstrumentations({ - instrumentations: [ - getNodeAutoInstrumentations({ - '@opentelemetry/instrumentation-http': { - startIncomingSpanHook: (req) => { - delete req.headers.traceparent - delete req.headers[`x-cloud-trace-context`] - delete req.headers[`grpc-trace-bin`] - - return {} - }, - }, - '@opentelemetry/instrumentation-fs': { - enabled: false, - }, - }), - ], -}) - -const resource = Resource.default().merge( - new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: process.env.TRACING_SERVICE_NAME, - [SemanticResourceAttributes.SERVICE_VERSION]: '0.1.0', - }) -) - -const provider = new NodeTracerProvider({ - resource, -}) -const exporter = new JaegerExporter(options) -const processor = new BatchSpanProcessor(exporter) -// @ts-ignore TODO @alec fix this -provider.addSpanProcessor(processor) - -provider.register() diff --git a/packages/phone-number-privacy/combiner/test/end-to-end/domain.test.ts b/packages/phone-number-privacy/combiner/test/end-to-end/domain.test.ts deleted file mode 100644 index 6ce612b026..0000000000 --- a/packages/phone-number-privacy/combiner/test/end-to-end/domain.test.ts +++ /dev/null @@ -1,338 +0,0 @@ -import { - BackupErrorTypes, - buildOdisDomain, - E2E_TESTING_ALFAJORES_CONFIG, - NO_QUOTA_ALFAJORES_CONFIG, - odisHardenKey, - odisQueryAuthorizer, - requestOdisDomainQuotaStatus, -} from '@celo/encrypted-backup' -import { OdisUtils } from '@celo/identity' -import { - CombinerEndpoint, - DisableDomainRequest, - disableDomainRequestEIP712, - disableDomainResponseSchema, - DisableDomainResponseSuccess, - domainHash, - DomainQuotaStatusResponseSuccess, - DomainRequestTypeTag, - DomainRestrictedSignatureRequest, - domainRestrictedSignatureRequestEIP712, - domainRestrictedSignatureResponseSchema, - DomainRestrictedSignatureResponseSuccess, - genSessionID, - KEY_VERSION_HEADER, - PoprfClient, - SequentialDelayDomain, - SequentialDelayDomainStateSchema, - TestUtils, -} from '@celo/phone-number-privacy-common' -import { defined, noNumber, noString } from '@celo/utils/lib/sign-typed-data-utils' -import * as crypto from 'crypto' -import 'isomorphic-fetch' -import { getCombinerVersion } from '../../src' -import { getTestContextName } from './resources' - -require('dotenv').config() - -jest.setTimeout(60000) - -const { ErrorMessages, getServiceContext, OdisAPI } = OdisUtils.Query - -const SERVICE_CONTEXT = getServiceContext(getTestContextName(), OdisAPI.DOMAIN) -const combinerUrl = SERVICE_CONTEXT.odisUrl -const fullNodeUrl = process.env.ODIS_BLOCKCHAIN_PROVIDER - -const authorizer = odisQueryAuthorizer(Buffer.from('combiner e2e authorizer test seed')) - -const domain = buildOdisDomain(E2E_TESTING_ALFAJORES_CONFIG.odis!, authorizer.address) - -const expectedVersion = getCombinerVersion() - -describe(`Running against service deployed at ${combinerUrl} w/ blockchain provider ${fullNodeUrl}`, () => { - it('Service is deployed at correct version', async () => { - const response = await fetch(combinerUrl + CombinerEndpoint.STATUS, { - method: 'GET', - }) - const body = await response.json() - // This checks against local package.json version, change if necessary - expect(body.version).toBe(expectedVersion) - }) - - describe(`${CombinerEndpoint.DOMAIN_QUOTA_STATUS}`, () => { - const testThatValidRequestSucceeds = async () => { - const res = await requestOdisDomainQuotaStatus( - domain, - SERVICE_CONTEXT, - genSessionID(), - authorizer.wallet - ) - - expect(res.ok).toBe(true) - if (res.ok) { - expect(res.result).toStrictEqual({ - success: true, - version: expectedVersion, - status: { - timer: res.result.status.timer, - counter: res.result.status.counter, - now: res.result.status.now, - disabled: false, - }, - }) - } - } - - it('Should succeed on valid request', async () => { - await testThatValidRequestSucceeds() - }) - - it('Should succeed on repeated valid requests', async () => { - await testThatValidRequestSucceeds() - await testThatValidRequestSucceeds() - }) - }) - - describe(`${CombinerEndpoint.DOMAIN_SIGN}`, () => { - const testThatValidRequestSucceeds = async () => { - const res = await odisHardenKey( - Buffer.from('password'), - domain, - SERVICE_CONTEXT, - authorizer.wallet - ) - // odisHardenKey verifies the signature against the service public key - expect(res.ok).toBe(true) - } - - it('Should succeed on valid request', async () => { - await testThatValidRequestSucceeds() - }) - - it('Should succeed on repeated valid request', async () => { - await testThatValidRequestSucceeds() - await testThatValidRequestSucceeds() - }) - - const signatureRequest = async ( - keyVersion: number, - nonce: number, - _domain: SequentialDelayDomain = domain - ): Promise<[DomainRestrictedSignatureRequest, PoprfClient]> => { - const poprfClient = new PoprfClient( - Buffer.from(TestUtils.Values.DOMAINS_THRESHOLD_DEV_PUBKEYS[keyVersion - 1], 'base64'), - domainHash(_domain), - Buffer.from('test message', 'utf8') - ) - - const req: DomainRestrictedSignatureRequest = { - type: DomainRequestTypeTag.SIGN, - domain: _domain, - options: { - signature: noString, - nonce: defined(nonce), - }, - blindedMessage: poprfClient.blindedMessage.toString('base64'), - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await authorizer.wallet.signTypedData( - authorizer.address, - domainRestrictedSignatureRequestEIP712(req) - ) - ) - return [req, poprfClient] - } - - for (let i = 1; i <= 2; i++) { - it(`Should succeed on valid request with key version header ${i}`, async () => { - const quotaRes = await requestOdisDomainQuotaStatus( - domain, - SERVICE_CONTEXT, - genSessionID(), - authorizer.wallet - ) - - let nonce = 0 - - expect(quotaRes.ok).toBe(true) - if (quotaRes.ok) { - expect(quotaRes.result).toStrictEqual({ - success: true, - version: expectedVersion, - status: { - timer: quotaRes.result.status.timer, - counter: quotaRes.result.status.counter, - now: quotaRes.result.status.now, - disabled: false, - }, - }) - nonce = quotaRes.result.status.counter - } - - const keyVersion = 1 - const [req, _] = await signatureRequest(keyVersion, nonce) - const res = await OdisUtils.Query.sendOdisDomainRequest( - req, - SERVICE_CONTEXT, - CombinerEndpoint.DOMAIN_SIGN, - domainRestrictedSignatureResponseSchema(SequentialDelayDomainStateSchema), - { - [KEY_VERSION_HEADER]: keyVersion.toString(), - } - ) - - expect(res.success).toBe(true) - if (res.success) { - expect(res).toStrictEqual({ - success: true, - version: expectedVersion, - signature: res.signature, - status: { - timer: res.status.timer, - counter: res.status.counter, - now: res.status.now, - disabled: false, - }, - }) - } - }) - } - - it('Should throw on invalid authentication', async () => { - const quotaRes = await requestOdisDomainQuotaStatus( - domain, - SERVICE_CONTEXT, - genSessionID(), - authorizer.wallet - ) - - let nonce = 0 - - expect(quotaRes.ok).toBe(true) - if (quotaRes.ok) { - expect(quotaRes.result).toStrictEqual({ - success: true, - version: expectedVersion, - status: { - timer: quotaRes.result.status.timer, - counter: quotaRes.result.status.counter, - now: quotaRes.result.status.now, - disabled: false, - }, - }) - nonce = quotaRes.result.status.counter - } - - const keyVersion = 1 - const [req, _] = await signatureRequest(keyVersion, nonce) - - req.domain.salt = defined('badSalt') - - await expect( - OdisUtils.Query.sendOdisDomainRequest( - req, - SERVICE_CONTEXT, - CombinerEndpoint.DOMAIN_SIGN, - domainRestrictedSignatureResponseSchema(SequentialDelayDomainStateSchema), - { - [KEY_VERSION_HEADER]: keyVersion.toString(), - } - ) - ).rejects.toThrow(ErrorMessages.ODIS_AUTH_ERROR) - }) - - it('Should return error on out of quota', async () => { - const noQuotaDomain = buildOdisDomain(NO_QUOTA_ALFAJORES_CONFIG.odis!, authorizer.address) - const res = await odisHardenKey( - Buffer.from('password'), - noQuotaDomain, - SERVICE_CONTEXT, - authorizer.wallet - ) - expect(res.ok).toBe(false) - if (!res.ok) { - expect(res.error.errorType).toEqual(BackupErrorTypes.ODIS_RATE_LIMITING_ERROR) - } - }) - }) - - describe(`${CombinerEndpoint.DISABLE_DOMAIN}`, () => { - const domainForDisabling = buildOdisDomain( - E2E_TESTING_ALFAJORES_CONFIG.odis!, - authorizer.address, - 'e2e testing, okay to disable ' + crypto.randomBytes(16).toString('base64') - ) - const disableRequest = async (): Promise> => { - const req: DisableDomainRequest = { - type: DomainRequestTypeTag.DISABLE, - domain: domainForDisabling, - options: { - signature: noString, - nonce: noNumber, - }, - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await authorizer.wallet.signTypedData(authorizer.address, disableDomainRequestEIP712(req)) - ) - return req - } - - const testThatDomainIsNotAlreadyDisabled = async () => { - const quotaRes = await requestOdisDomainQuotaStatus( - domainForDisabling, - SERVICE_CONTEXT, - genSessionID(), - authorizer.wallet - ) - expect(quotaRes.ok).toBe(true) - if (quotaRes.ok) { - expect(quotaRes.result).toStrictEqual({ - success: true, - version: expectedVersion, - status: { - timer: quotaRes.result.status.timer, - counter: quotaRes.result.status.counter, - now: quotaRes.result.status.now, - disabled: false, // checking that domain is not already disabled - }, - }) - } - } - - const testThatValidRequestSucceeds = async () => { - const res = await OdisUtils.Query.sendOdisDomainRequest( - await disableRequest(), - SERVICE_CONTEXT, - CombinerEndpoint.DISABLE_DOMAIN, - disableDomainResponseSchema(SequentialDelayDomainStateSchema) - ) - - expect(res.success).toBe(true) - if (res.success) { - expect(res).toStrictEqual({ - success: true, - version: expectedVersion, - status: { - timer: res.status.timer, - now: res.status.now, - disabled: true, // checking that domain is now disabled - counter: res.status.counter, - }, - }) - } - } - - it('Should succeed on valid request', async () => { - await testThatDomainIsNotAlreadyDisabled() - await testThatValidRequestSucceeds() - }) - - it('Should succeed on repeated valid request', async () => { - await testThatValidRequestSucceeds() - await testThatValidRequestSucceeds() - }) - }) -}) diff --git a/packages/phone-number-privacy/combiner/test/end-to-end/pnp.test.ts b/packages/phone-number-privacy/combiner/test/end-to-end/pnp.test.ts deleted file mode 100644 index 63f01d1729..0000000000 --- a/packages/phone-number-privacy/combiner/test/end-to-end/pnp.test.ts +++ /dev/null @@ -1,470 +0,0 @@ -import { sleep } from '@celo/base' -import { StableToken } from '@celo/contractkit' -import { OdisUtils } from '@celo/identity' -import { ErrorMessages, getServiceContext, OdisAPI } from '@celo/identity/lib/odis/query' -import { PnpClientQuotaStatus } from '@celo/identity/lib/odis/quota' -import { - CombinerEndpoint, - PnpQuotaRequest, - PnpQuotaResponseSchema, - SignMessageRequest, - SignMessageResponseSchema, -} from '@celo/phone-number-privacy-common' -import { normalizeAddressWith0x } from '@celo/utils/lib/address' -import threshold_bls from 'blind-threshold-bls' -import { randomBytes } from 'crypto' -import 'isomorphic-fetch' -import { config as signerConfig } from '../../../signer/src/config' -import { getCombinerVersion } from '../../src' -import { - ACCOUNT_ADDRESS, - ACCOUNT_ADDRESS_NO_QUOTA, - BLINDED_PHONE_NUMBER, - dekAuthSigner, - deks, - getTestContextName, - PHONE_NUMBER, - walletAuthSigner, -} from './resources' - -const { IdentifierPrefix } = OdisUtils.Identifier - -require('dotenv').config() - -jest.setTimeout(60000) - -const SERVICE_CONTEXT = getServiceContext(getTestContextName(), OdisAPI.PNP) -const combinerUrl = SERVICE_CONTEXT.odisUrl -const fullNodeUrl = process.env.ODIS_BLOCKCHAIN_PROVIDER - -const expectedVersion = getCombinerVersion() - -describe(`Running against service deployed at ${combinerUrl} w/ blockchain provider ${fullNodeUrl}`, () => { - beforeAll(async () => { - const accounts = await walletAuthSigner.contractKit.contracts.getAccounts() - const dekPublicKey = normalizeAddressWith0x(deks[0].publicKey) - if ((await accounts.getDataEncryptionKey(ACCOUNT_ADDRESS)) !== dekPublicKey) { - await accounts - .setAccountDataEncryptionKey(dekPublicKey) - .sendAndWaitForReceipt({ from: ACCOUNT_ADDRESS }) - } - }) - - it('Service is deployed at correct version', async () => { - const response = await fetch(combinerUrl + CombinerEndpoint.STATUS, { - method: 'GET', - }) - const body = await response.json() - // This checks against local package.json version, change if necessary - expect(body.version).toBe(expectedVersion) - }) - - describe(`${CombinerEndpoint.PNP_QUOTA}`, () => { - it('Should succeed when authenticated with WALLET_KEY', async () => { - const res = await OdisUtils.Quota.getPnpQuotaStatus( - ACCOUNT_ADDRESS, - walletAuthSigner, - SERVICE_CONTEXT - ) - expect(res).toStrictEqual({ - version: expectedVersion, - performedQueryCount: res.performedQueryCount, - totalQuota: res.totalQuota, - remainingQuota: res.totalQuota - res.performedQueryCount, - warnings: res.warnings ?? [], - }) - }) - - it('Should succeed when authenticated with DEK', async () => { - const res = await OdisUtils.Quota.getPnpQuotaStatus( - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT - ) - expect(res).toStrictEqual({ - version: expectedVersion, - performedQueryCount: res.performedQueryCount, - totalQuota: res.totalQuota, - remainingQuota: res.totalQuota - res.performedQueryCount, - warnings: res.warnings ?? [], - }) - }) - - it('Should succeed on repeated valid requests', async () => { - const res1 = await OdisUtils.Quota.getPnpQuotaStatus( - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT - ) - const expectedRes: PnpClientQuotaStatus = { - version: expectedVersion, - performedQueryCount: res1.performedQueryCount, - totalQuota: res1.totalQuota, - remainingQuota: res1.totalQuota - res1.performedQueryCount, - warnings: res1.warnings ?? [], - } - expect(res1).toStrictEqual(expectedRes) - const res2 = await OdisUtils.Quota.getPnpQuotaStatus( - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT - ) - expect(res2).toStrictEqual(expectedRes) - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_INPUT_ERROR} with invalid address`, async () => { - await expect( - OdisUtils.Quota.getPnpQuotaStatus('not an address', dekAuthSigner(0), SERVICE_CONTEXT) - ).rejects.toThrow(ErrorMessages.ODIS_INPUT_ERROR) - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_AUTH_ERROR} with invalid WALLET_KEY auth`, async () => { - const req: PnpQuotaRequest = { - account: ACCOUNT_ADDRESS, - authenticationMethod: walletAuthSigner.authenticationMethod, - } - await expect( - OdisUtils.Query.queryOdis( - req, - SERVICE_CONTEXT, - CombinerEndpoint.PNP_QUOTA, - PnpQuotaResponseSchema, - { - Authorization: await walletAuthSigner.contractKit.connection.sign( - JSON.stringify(req), - ACCOUNT_ADDRESS_NO_QUOTA - ), - } - ) - ).rejects.toThrow(ErrorMessages.ODIS_AUTH_ERROR) - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_AUTH_ERROR} with invalid DEK auth`, async () => { - await expect( - OdisUtils.Quota.getPnpQuotaStatus( - ACCOUNT_ADDRESS, - dekAuthSigner(1), // DEK auth signer doesn't match the registered DEK for ACCOUNT_ADDRESS - SERVICE_CONTEXT - ) - ).rejects.toThrow(ErrorMessages.ODIS_AUTH_ERROR) - }) - }) - - describe(`${CombinerEndpoint.PNP_SIGN}`, () => { - beforeAll(async () => { - // Replenish quota for ACCOUNT_ADDRESS - // If this fails, may be necessary to faucet ACCOUNT_ADDRESS more funds - const numQueriesToReplenish = 100 - const amountInWei = signerConfig.quota.queryPriceInCUSD - .times(1e18) - .times(numQueriesToReplenish) - .toString() - const stableToken = await walletAuthSigner.contractKit.contracts.getStableToken( - StableToken.cUSD - ) - const odisPayments = await walletAuthSigner.contractKit.contracts.getOdisPayments() - await stableToken - .approve(odisPayments.address, amountInWei) - .sendAndWaitForReceipt({ from: ACCOUNT_ADDRESS }) - await odisPayments - .payInCUSD(ACCOUNT_ADDRESS, amountInWei) - .sendAndWaitForReceipt({ from: ACCOUNT_ADDRESS }) - // wait for cache to expire and then query to refresh - await sleep(5 * 1000) - await OdisUtils.Quota.getPnpQuotaStatus(ACCOUNT_ADDRESS, dekAuthSigner(0), SERVICE_CONTEXT) - }) - - describe('new requests', () => { - // Requests made for PHONE_NUMBER from ACCOUNT_ADDRESS & same blinding factor - // are replayed from previous test runs (for every run after the very first) - let startingPerformedQueryCount: number - let startingTotalQuota: number - beforeEach(async () => { - const res = await OdisUtils.Quota.getPnpQuotaStatus( - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT - ) - startingPerformedQueryCount = res.performedQueryCount - startingTotalQuota = res.totalQuota - }) - - it('Should increment performedQueryCount on success with DEK auth', async () => { - // Raw key is used as the blinding client's seed, so we need a new PN - // Create a fake PN that is always incrementing and shouldn't ever repeat - const unusedPN = `+1${Date.now()}` - await OdisUtils.Identifier.getObfuscatedIdentifier( - unusedPN, - IdentifierPrefix.PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT - ) - const quotaRes = await OdisUtils.Quota.getPnpQuotaStatus( - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT - ) - expect(quotaRes).toStrictEqual({ - version: expectedVersion, - performedQueryCount: startingPerformedQueryCount + 1, - totalQuota: startingTotalQuota, - remainingQuota: startingTotalQuota - (startingPerformedQueryCount + 1), - warnings: [ - 'CELO_ODIS_WARN_17 SIGNER Discrepancies detected in signer responses', - 'CELO_ODIS_WARN_18 SIGNER Discrepancy found in signers performed query count measurements', - ], - }) - }) - - it('Should increment performedQueryCount on success with WALLET_KEY auth', async () => { - await OdisUtils.Identifier.getObfuscatedIdentifier( - PHONE_NUMBER, - IdentifierPrefix.PHONE_NUMBER, - ACCOUNT_ADDRESS, - walletAuthSigner, - SERVICE_CONTEXT, - Buffer.from(randomBytes(32)).toString('base64') - ) - const quotaRes = await OdisUtils.Quota.getPnpQuotaStatus( - ACCOUNT_ADDRESS, - walletAuthSigner, - SERVICE_CONTEXT - ) - expect(quotaRes).toStrictEqual({ - version: expectedVersion, - performedQueryCount: startingPerformedQueryCount + 1, - totalQuota: startingTotalQuota, - remainingQuota: startingTotalQuota - (startingPerformedQueryCount + 1), - warnings: [ - 'CELO_ODIS_WARN_17 SIGNER Discrepancies detected in signer responses', - 'CELO_ODIS_WARN_18 SIGNER Discrepancy found in signers performed query count measurements', - ], - }) - }) - }) - - describe('replayed requests', () => { - const replayedBlindingFactor = Buffer.from('test string for blinding factor').toString( - 'base64' - ) - beforeAll(async () => { - // Ensure that these are each called at least once for the first test runs - await OdisUtils.Identifier.getObfuscatedIdentifier( - PHONE_NUMBER, - IdentifierPrefix.PHONE_NUMBER, - ACCOUNT_ADDRESS, - walletAuthSigner, - SERVICE_CONTEXT, - replayedBlindingFactor - ) - await OdisUtils.Identifier.getObfuscatedIdentifier( - PHONE_NUMBER, - IdentifierPrefix.PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT - ) - }) - - // Requests made for PHONE_NUMBER from ACCOUNT_ADDRESS - // are replayed from previous test runs (for every run after the very first) - let startingPerformedQueryCount: number - let startingTotalQuota: number - beforeEach(async () => { - const res = await OdisUtils.Quota.getPnpQuotaStatus( - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT - ) - startingPerformedQueryCount = res.performedQueryCount - startingTotalQuota = res.totalQuota - }) - - it('Should succeed and not update performedQueryCount when authenticated with WALLET_KEY', async () => { - const res = await OdisUtils.Identifier.getObfuscatedIdentifier( - PHONE_NUMBER, - IdentifierPrefix.PHONE_NUMBER, - ACCOUNT_ADDRESS, - walletAuthSigner, - SERVICE_CONTEXT, - replayedBlindingFactor - ) - threshold_bls.verify( - Buffer.from(SERVICE_CONTEXT.odisPubKey, 'base64'), - Buffer.from(PHONE_NUMBER), - Buffer.from(res.unblindedSignature!, 'base64') - ) - const quotaRes = await OdisUtils.Quota.getPnpQuotaStatus( - ACCOUNT_ADDRESS, - walletAuthSigner, - SERVICE_CONTEXT - ) - expect(quotaRes.performedQueryCount).toEqual(startingPerformedQueryCount) - expect(quotaRes.totalQuota).toEqual(startingTotalQuota) - }) - - it('Should succeed and not update performedQueryCount when authenticated with DEK', async () => { - const res = await OdisUtils.Identifier.getObfuscatedIdentifier( - PHONE_NUMBER, - IdentifierPrefix.PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT - ) - threshold_bls.verify( - Buffer.from(SERVICE_CONTEXT.odisPubKey, 'base64'), - Buffer.from(PHONE_NUMBER), - Buffer.from(res.unblindedSignature!, 'base64') - ) - const quotaRes = await OdisUtils.Quota.getPnpQuotaStatus( - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT - ) - expect(quotaRes.performedQueryCount).toEqual(startingPerformedQueryCount) - expect(quotaRes.totalQuota).toEqual(startingTotalQuota) - }) - }) - - // NOTE: these are also replayed requests - for (let i = 1; i <= 2; i++) { - it(`Should succeed on valid request with key version header ${i}`, async () => { - const res = await OdisUtils.Identifier.getObfuscatedIdentifier( - PHONE_NUMBER, - IdentifierPrefix.PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - i - ) - threshold_bls.verify( - Buffer.from(SERVICE_CONTEXT.odisPubKey, 'base64'), - Buffer.from(PHONE_NUMBER), - Buffer.from(res.unblindedSignature!, 'base64') - ) - }) - } - - it(`Should succeed on invalid key version`, async () => { - const res = await OdisUtils.Identifier.getObfuscatedIdentifier( - PHONE_NUMBER, - IdentifierPrefix.PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - 1.5 - ) - threshold_bls.verify( - Buffer.from(SERVICE_CONTEXT.odisPubKey, 'base64'), - Buffer.from(PHONE_NUMBER), - Buffer.from(res.unblindedSignature!, 'base64') - ) - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_INPUT_ERROR} on unsupported key version`, async () => { - await expect( - OdisUtils.Identifier.getObfuscatedIdentifier( - PHONE_NUMBER, - IdentifierPrefix.PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - 10 - ) - ).rejects.toThrow(ErrorMessages.ODIS_INPUT_ERROR) - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_INPUT_ERROR} on invalid address`, async () => { - await expect( - OdisUtils.Identifier.getObfuscatedIdentifier( - PHONE_NUMBER, - IdentifierPrefix.PHONE_NUMBER, - 'not an address', - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - 1 - ) - ).rejects.toThrow(ErrorMessages.ODIS_INPUT_ERROR) - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_INPUT_ERROR} on invalid phone number`, async () => { - await expect( - OdisUtils.Identifier.getObfuscatedIdentifier( - '12345', - IdentifierPrefix.PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - 1 - ) - ).rejects.toThrow('Invalid phone number: 12345') - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_AUTH_ERROR} with invalid WALLET_KEY auth`, async () => { - const req: SignMessageRequest = { - account: ACCOUNT_ADDRESS, - blindedQueryPhoneNumber: BLINDED_PHONE_NUMBER, - authenticationMethod: walletAuthSigner.authenticationMethod, - } - await expect( - OdisUtils.Query.queryOdis( - req, - SERVICE_CONTEXT, - CombinerEndpoint.PNP_SIGN, - SignMessageResponseSchema, - { - Authorization: await walletAuthSigner.contractKit.connection.sign( - JSON.stringify(req), - ACCOUNT_ADDRESS_NO_QUOTA - ), - } - ) - ).rejects.toThrow(ErrorMessages.ODIS_AUTH_ERROR) - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_AUTH_ERROR} with invalid DEK auth`, async () => { - await expect( - OdisUtils.Identifier.getObfuscatedIdentifier( - PHONE_NUMBER, - IdentifierPrefix.PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(1), // DEK auth signer doesn't match the registered DEK for ACCOUNT_ADDRESS - SERVICE_CONTEXT - ) - ).rejects.toThrow(ErrorMessages.ODIS_AUTH_ERROR) - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_QUOTA_ERROR} when account has no quota`, async () => { - await expect( - OdisUtils.Identifier.getObfuscatedIdentifier( - PHONE_NUMBER, - IdentifierPrefix.PHONE_NUMBER, - ACCOUNT_ADDRESS_NO_QUOTA, - dekAuthSigner(0), - SERVICE_CONTEXT - ) - ).rejects.toThrow(ErrorMessages.ODIS_QUOTA_ERROR) - }) - }) -}) diff --git a/packages/phone-number-privacy/combiner/test/end-to-end/resources.ts b/packages/phone-number-privacy/combiner/test/end-to-end/resources.ts deleted file mode 100644 index bd93afa69d..0000000000 --- a/packages/phone-number-privacy/combiner/test/end-to-end/resources.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { newKit } from '@celo/contractkit' -import { - EncryptionKeySigner, - OdisContextName, - WalletKeySigner, -} from '@celo/identity/lib/odis/query' -import { AuthenticationMethod } from '@celo/phone-number-privacy-common' -import { PhoneNumberUtils } from '@celo/phone-utils' -import { - ensureLeading0x, - normalizeAddressWith0x, - privateKeyToAddress, -} from '@celo/utils/lib/address' -import 'isomorphic-fetch' - -require('dotenv').config() - -export const getTestContextName = (): OdisContextName => { - switch (process.env.CONTEXT_NAME) { - case 'alfajores': - return OdisContextName.ALFAJORES - case 'staging': - return OdisContextName.STAGING - case 'mainnet': - return OdisContextName.MAINNET - default: - throw new Error('CONTEXT_NAME env var is undefined or invalid') - } -} - -/** - * CONSTS - */ -export const DEFAULT_FORNO_URL = - process.env.ODIS_BLOCKCHAIN_PROVIDER ?? 'https://alfajores-forno.celo-testnet.org' - -export const PRIVATE_KEY = '2c63bf6d60b16c8afa13e1069dbe92fef337c23855fff8b27732b3e9c6e7efd4' -export const ACCOUNT_ADDRESS = normalizeAddressWith0x(privateKeyToAddress(PRIVATE_KEY)) // 0x6037800e91eaa703e38bad40c01410bbdf0fea7e - -export const PRIVATE_KEY_NO_QUOTA = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890000000' -export const ACCOUNT_ADDRESS_NO_QUOTA = privateKeyToAddress(PRIVATE_KEY_NO_QUOTA) - -export const PHONE_NUMBER = '+17777777777' -export const BLINDING_FACTOR = Buffer.from('0IsBvRfkBrkKCIW6HV0/T1zrzjQSe8wRyU3PKojCnww=', 'base64') -// BLINDED_PHONE_NUMBER value is dependent on PHONE_NUMBER AND BLINDING_FACTOR -// hardcoding to avoid importing blind_threshols_bls library -export const BLINDED_PHONE_NUMBER = - 'hZXDhpC5onzBSFa1agZ9vfHzqwJ/QeJg77NGvWiQG/sFWsvHETzZvdWr2GpF3QkB' - -export const PHONE_HASH_IDENTIFIER = PhoneNumberUtils.getPhoneHash(PHONE_NUMBER) - -export const CONTACT_PHONE_NUMBER = '+14155559999' -export const CONTACT_PHONE_NUMBERS = [CONTACT_PHONE_NUMBER] - -/** - * RESOURCES AND UTILS - */ -export const contractKit = newKit(DEFAULT_FORNO_URL) -contractKit.addAccount(PRIVATE_KEY_NO_QUOTA) -contractKit.addAccount(PRIVATE_KEY) -contractKit.defaultAccount = ACCOUNT_ADDRESS - -interface DEK { - privateKey: string - publicKey: string - address: string -} - -export const deks: DEK[] = [ - { - privateKey: 'bf8a2b73baf8402f8fe906ad3f42b560bf14b39f7df7797ece9e293d6f162188', - publicKey: '034846bc781cacdafc66f3a77aa9fc3c56a9dadcd683c72be3c446fee8da041070', - address: '0x7b33dF2607b85e3211738a49A6Ad6E8Ed4d13F6E', - }, - { - privateKey: '0975b0c565abc75b6638a749ea3008cb52676af3eabe4b80e19c516d82330364', - publicKey: '03b1ac8c445f0796978018c087b97e8213b32c39e6a8642ae63dce71da33a19f65', - address: '0x34332049B07Fab9a2e843A7C8991469d93cF6Ae6', - }, -] -// The following code can be used to generate more test DEKs -// const generateDEKs = (n: number): Promise => Promise.all([...Array(n).keys()].map( -// async () => await deriveDek(await generateMnemonic()) -// )) - -export const dekAuthSigner = (index: number): EncryptionKeySigner => { - return { - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - rawKey: ensureLeading0x(deks[index].privateKey), - } -} - -export const walletAuthSigner: WalletKeySigner = { - authenticationMethod: AuthenticationMethod.WALLET_KEY, - contractKit, -} diff --git a/packages/phone-number-privacy/combiner/test/integration/domain.test.ts b/packages/phone-number-privacy/combiner/test/integration/domain.test.ts deleted file mode 100644 index c997f5b5ee..0000000000 --- a/packages/phone-number-privacy/combiner/test/integration/domain.test.ts +++ /dev/null @@ -1,1292 +0,0 @@ -import { - CombinerEndpoint, - DB_TIMEOUT, - DisableDomainRequest, - disableDomainRequestEIP712, - DisableDomainResponse, - domainHash, - DomainIdentifiers, - DomainQuotaStatusRequest, - domainQuotaStatusRequestEIP712, - DomainQuotaStatusResponse, - DomainRequestTypeTag, - DomainRestrictedSignatureRequest, - domainRestrictedSignatureRequestEIP712, - DomainRestrictedSignatureResponse, - ErrorMessage, - FULL_NODE_TIMEOUT_IN_MS, - genSessionID, - getContractKitWithAgent, - KEY_VERSION_HEADER, - PoprfClient, - RETRY_COUNT, - RETRY_DELAY_IN_MS, - SequentialDelayDomain, - SequentialDelayStage, - TestUtils, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { initDatabase as initSignerDatabase } from '@celo/phone-number-privacy-signer/dist/common/database/database' -import { startSigner } from '@celo/phone-number-privacy-signer/dist/server' -import { SupportedDatabase, SupportedKeystore } from '@celo/phone-number-privacy-signer/dist/config' -import { - DefaultKeyName, - KeyProvider, -} from '@celo/phone-number-privacy-signer/dist/common/key-management/key-provider-base' -import { SignerConfig } from '@celo/phone-number-privacy-signer/dist/config' -import { defined, noBool, noNumber, noString } from '@celo/utils/lib/sign-typed-data-utils' -import { LocalWallet } from '@celo/wallet-local' -import BigNumber from 'bignumber.js' -import { Server as HttpsServer } from 'https' -import { Knex } from 'knex' -import { Server } from 'http' -import request from 'supertest' -import { MockKeyProvider } from '../../../signer/dist/common/key-management/mock-key-provider' -import config from '../../src/config' -import { startCombiner } from '../../src/server' -import { serverClose } from '../utils' - -const { - DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V1, - DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V2, - DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V3, - DOMAINS_THRESHOLD_DEV_PK_SHARE_2_V1, - DOMAINS_THRESHOLD_DEV_PK_SHARE_2_V2, - DOMAINS_THRESHOLD_DEV_PK_SHARE_2_V3, - DOMAINS_THRESHOLD_DEV_PK_SHARE_3_V1, - DOMAINS_THRESHOLD_DEV_PK_SHARE_3_V2, - DOMAINS_THRESHOLD_DEV_PK_SHARE_3_V3, -} = TestUtils.Values - -// create deep copy of config -const combinerConfig: typeof config = JSON.parse(JSON.stringify(config)) -combinerConfig.domains.enabled = true - -const signerConfig: SignerConfig = { - serviceName: 'odis-signer', - server: { - port: undefined, - sslKeyPath: undefined, - sslCertPath: undefined, - }, - quota: { - unverifiedQueryMax: 10, - additionalVerifiedQueryMax: 30, - queryPerTransaction: 2, - // Min balance is .01 cUSD - minDollarBalance: new BigNumber(1e16), - // Min balance is .01 cEUR - minEuroBalance: new BigNumber(1e16), - // Min balance is .005 CELO - minCeloBalance: new BigNumber(5e15), - // Equivalent to 0.001 cUSD/query - queryPriceInCUSD: new BigNumber(0.001), - }, - api: { - domains: { - enabled: true, - }, - phoneNumberPrivacy: { - enabled: false, - }, - }, - blockchain: { - provider: 'https://alfajores-forno.celo-testnet.org', - apiKey: undefined, - }, - db: { - type: SupportedDatabase.Sqlite, - user: '', - password: '', - database: '', - host: 'http://localhost', - port: undefined, - ssl: true, - poolMaxSize: 50, - timeout: DB_TIMEOUT, - }, - keystore: { - type: SupportedKeystore.MOCK_SECRET_MANAGER, - keys: { - phoneNumberPrivacy: { - name: 'phoneNumberPrivacy', - latest: 2, - }, - domains: { - name: 'domains', - latest: 1, - }, - }, - azure: { - clientID: '', - clientSecret: '', - tenant: '', - vaultName: '', - }, - google: { - projectId: '', - }, - aws: { - region: '', - secretKey: '', - }, - }, - timeout: 5000, - test_quota_bypass_percentage: 0, - fullNodeTimeoutMs: FULL_NODE_TIMEOUT_IN_MS, - fullNodeRetryCount: RETRY_COUNT, - fullNodeRetryDelayMs: RETRY_DELAY_IN_MS, - // TODO (alec) make SignerConfig better - shouldMockAccountService: false, - mockDek: '', - mockTotalQuota: 0, - shouldMockRequestService: false, - requestPrunningDays: 0, - requestPrunningAtServerStart: false, - requestPrunningJobCronPattern: '0 0 * * * *', -} - -describe('domainService', () => { - const wallet = new LocalWallet() - wallet.addAccount('0x00000000000000000000000000000000000000000000000000000000deadbeef') - const walletAddress = wallet.getAccounts()[0]! - - const domainStages = (): SequentialDelayStage[] => [ - { delay: 0, resetTimer: noBool, batchSize: defined(2), repetitions: defined(10) }, - ] - - const authenticatedDomain = (_stages?: SequentialDelayStage[]): SequentialDelayDomain => ({ - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: _stages ?? domainStages(), - address: defined(walletAddress), - salt: defined('himalayanPink'), - }) - - const DEFAULT_PUB_KEY = - TestUtils.Values.DOMAINS_THRESHOLD_DEV_PUBKEYS[config.domains.keys.currentVersion - 1] - const signatureRequest = async ( - _domain?: SequentialDelayDomain, - _nonce?: number, - _pubKey: string = DEFAULT_PUB_KEY - ): Promise<[DomainRestrictedSignatureRequest, PoprfClient]> => { - const domain = _domain ?? authenticatedDomain() - const poprfClient = new PoprfClient( - Buffer.from(_pubKey, 'base64'), - domainHash(domain), - Buffer.from('test message', 'utf8') - ) - - const req: DomainRestrictedSignatureRequest = { - type: DomainRequestTypeTag.SIGN, - domain: domain, - options: { - signature: noString, - nonce: defined(_nonce ?? 0), - }, - blindedMessage: poprfClient.blindedMessage.toString('base64'), - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await wallet.signTypedData(walletAddress, domainRestrictedSignatureRequestEIP712(req)) - ) - return [req, poprfClient] - } - - const quotaRequest = async (): Promise> => { - const req: DomainQuotaStatusRequest = { - type: DomainRequestTypeTag.QUOTA, - domain: authenticatedDomain(), - options: { - signature: noString, - nonce: noNumber, - }, - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await wallet.signTypedData(walletAddress, domainQuotaStatusRequestEIP712(req)) - ) - return req - } - - // Build and sign an example disable domain request. - const disableRequest = async ( - _domain?: SequentialDelayDomain - ): Promise> => { - const req: DisableDomainRequest = { - type: DomainRequestTypeTag.DISABLE, - domain: _domain ?? authenticatedDomain(), - options: { - signature: noString, - nonce: noNumber, - }, - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await wallet.signTypedData(walletAddress, disableDomainRequestEIP712(req)) - ) - return req - } - - let keyProvider1: KeyProvider - let keyProvider2: KeyProvider - let keyProvider3: KeyProvider - let signerDB1: Knex - let signerDB2: Knex - let signerDB3: Knex - let signer1: Server | HttpsServer - let signer2: Server | HttpsServer - let signer3: Server | HttpsServer - let app: any - - const signerMigrationsPath = '../signer/dist/common/database/migrations' - - describe('with n=3, t=2', () => { - const expectedEvals: string[] = [ - '3QLFPV6VvnhhnZ7mOu0xm7BUUJIUVY6vEHvZONOtZ/c=', - 'BBG0fAZJ6VNQwjge+3vOCF3uBo5KCs2+er/f/2QcV58=', - '1/otd1fW1nhUoU3ubjFDS8/RX0OClvHDsmGdnz6fZVE=', - ] - const expectedEval = expectedEvals[config.domains.keys.currentVersion - 1] - - beforeAll(async () => { - keyProvider1 = new MockKeyProvider( - new Map([ - [`${DefaultKeyName.DOMAINS}-1`, DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V1], - [`${DefaultKeyName.DOMAINS}-2`, DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V2], - [`${DefaultKeyName.DOMAINS}-3`, DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V3], - ]) - ) - keyProvider2 = new MockKeyProvider( - new Map([ - [`${DefaultKeyName.DOMAINS}-1`, DOMAINS_THRESHOLD_DEV_PK_SHARE_2_V1], - [`${DefaultKeyName.DOMAINS}-2`, DOMAINS_THRESHOLD_DEV_PK_SHARE_2_V2], - [`${DefaultKeyName.DOMAINS}-3`, DOMAINS_THRESHOLD_DEV_PK_SHARE_2_V3], - ]) - ) - keyProvider3 = new MockKeyProvider( - new Map([ - [`${DefaultKeyName.DOMAINS}-1`, DOMAINS_THRESHOLD_DEV_PK_SHARE_3_V1], - [`${DefaultKeyName.DOMAINS}-2`, DOMAINS_THRESHOLD_DEV_PK_SHARE_3_V2], - [`${DefaultKeyName.DOMAINS}-3`, DOMAINS_THRESHOLD_DEV_PK_SHARE_3_V3], - ]) - ) - - app = startCombiner(combinerConfig, getContractKitWithAgent(combinerConfig.blockchain)) - }) - - beforeEach(async () => { - signerDB1 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB2 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB3 = await initSignerDatabase(signerConfig, signerMigrationsPath) - }) - - afterEach(async () => { - await signerDB1?.destroy() - await signerDB2?.destroy() - await signerDB3?.destroy() - await serverClose(signer1) - await serverClose(signer2) - await serverClose(signer3) - }) - - describe('when signers are operating correctly', () => { - beforeEach(async () => { - signer1 = startSigner(signerConfig, signerDB1, keyProvider1).listen(3001) - signer2 = startSigner(signerConfig, signerDB2, keyProvider2).listen(3002) - signer3 = startSigner(signerConfig, signerDB3, keyProvider3).listen(3003) - }) - - describe(`${CombinerEndpoint.DISABLE_DOMAIN}`, () => { - it('Should respond with 200 on valid request', async () => { - const res = await request(app) - .post(CombinerEndpoint.DISABLE_DOMAIN) - .send(await disableRequest()) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - status: { disabled: true, counter: 0, timer: 0, now: res.body.status.now }, - }) - }) - - it('Should respond with 200 on repeated valid requests', async () => { - const req = await disableRequest() - const res1 = await request(app).post(CombinerEndpoint.DISABLE_DOMAIN).send(req) - expect(res1.status).toBe(200) - const expectedResponse: DisableDomainResponse = { - success: true, - version: res1.body.version, - status: { disabled: true, counter: 0, timer: 0, now: res1.body.status.now }, - } - expect(res1.body).toStrictEqual(expectedResponse) - const res2 = await request(app).post(CombinerEndpoint.DISABLE_DOMAIN).send(req) - expect(res2.status).toBe(200) - expectedResponse.status.now = res2.body.status.now - expect(res2.body).toStrictEqual(expectedResponse) - }) - - it('Should respond with 200 on extra request fields', async () => { - const req = await disableRequest() - // @ts-ignore Intentionally adding an extra field to the request type - req.options.extraField = noString - - const res = await request(app).post(CombinerEndpoint.DISABLE_DOMAIN).send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - status: { disabled: true, counter: 0, timer: 0, now: res.body.status.now }, - }) - }) - - it('Should respond with 400 on missing request fields', async () => { - const badRequest = await disableRequest() - // @ts-ignore Intentionally deleting required field - delete badRequest.domain.version - - const res = await request(app).post(CombinerEndpoint.DISABLE_DOMAIN).send(badRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on unknown domain', async () => { - // Create a requests with an invalid domain identifier. - const unknownRequest = await disableRequest() - // @ts-ignore UnknownDomain is (intentionally) not a valid domain identifier. - unknownRequest.domain.name = 'UnknownDomain' - - const res = await request(app).post(CombinerEndpoint.DISABLE_DOMAIN).send(unknownRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on bad encoding', async () => { - const badRequest1 = await disableRequest() - // @ts-ignore Intentionally not JSON - badRequest1.domain = 'Freddy' - - const res1 = await request(app).post(CombinerEndpoint.DISABLE_DOMAIN).send(badRequest1) - - expect(res1.status).toBe(400) - expect(res1.body).toStrictEqual({ - success: false, - version: res1.body.version, - error: WarningMessage.INVALID_INPUT, - }) - - const badRequest2 = '' - - const res2 = await request(app).post(CombinerEndpoint.DISABLE_DOMAIN).send(badRequest2) - - expect(res2.status).toBe(400) - expect(res2.body).toStrictEqual({ - success: false, - version: res2.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed auth', async () => { - // Create a manipulated request, which will have a bad signature. - const badRequest = await disableRequest() - badRequest.domain.salt = defined('badSalt') - - const res = await request(app).post(CombinerEndpoint.DISABLE_DOMAIN).send(badRequest) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 503 on disabled api', async () => { - const configWithApiDisabled: typeof combinerConfig = JSON.parse( - JSON.stringify(combinerConfig) - ) - configWithApiDisabled.domains.enabled = false - const appWithApiDisabled = startCombiner( - configWithApiDisabled, - getContractKitWithAgent(configWithApiDisabled.blockchain) - ) - const req = await disableRequest() - - const res = await request(appWithApiDisabled) - .post(CombinerEndpoint.DISABLE_DOMAIN) - .send(req) - - expect(res.status).toBe(503) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - }) - - describe(`${CombinerEndpoint.DOMAIN_QUOTA_STATUS}`, () => { - it('Should respond with 200 on valid request', async () => { - const res = await request(app) - .post(CombinerEndpoint.DOMAIN_QUOTA_STATUS) - .send(await quotaRequest()) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - status: { disabled: false, counter: 0, timer: 0, now: res.body.status.now }, - }) - }) - - it('Should respond with 200 on repeated valid requests', async () => { - const req = await quotaRequest() - const res1 = await request(app).post(CombinerEndpoint.DOMAIN_QUOTA_STATUS).send(req) - const expectedResponse: DomainQuotaStatusResponse = { - success: true, - version: res1.body.version, - status: { disabled: false, counter: 0, timer: 0, now: res1.body.status.now }, - } - - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual(expectedResponse) - - const res2 = await request(app).post(CombinerEndpoint.DOMAIN_QUOTA_STATUS).send(req) - expect(res2.status).toBe(200) - // Prevent flakiness due to slight timing inconsistencies - expectedResponse.status.now = res2.body.status.now - expect(res2.body).toStrictEqual(expectedResponse) - }) - - it('Should respond with 200 on extra request fields', async () => { - const req = await quotaRequest() - // @ts-ignore Intentionally adding an extra field to the request type - req.options.extraField = noString - - const res = await request(app).post(CombinerEndpoint.DOMAIN_QUOTA_STATUS).send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - status: { disabled: false, counter: 0, timer: 0, now: res.body.status.now }, - }) - }) - - it('Should respond with 400 on missing request fields', async () => { - const badRequest = await quotaRequest() - // @ts-ignore Intentionally deleting required field - delete badRequest.domain.version - - const res = await request(app).post(CombinerEndpoint.DOMAIN_QUOTA_STATUS).send(badRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on unknown domain', async () => { - // Create a requests with an invalid domain identifier. - const unknownRequest = await quotaRequest() - // @ts-ignore UnknownDomain is (intentionally) not a valid domain identifier. - unknownRequest.domain.name = 'UnknownDomain' - - const res = await request(app) - .post(CombinerEndpoint.DOMAIN_QUOTA_STATUS) - .send(unknownRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on bad encoding', async () => { - const badRequest1 = await quotaRequest() - // @ts-ignore Intentionally not JSON - badRequest1.domain = 'Freddy' - - const res1 = await request(app) - .post(CombinerEndpoint.DOMAIN_QUOTA_STATUS) - .send(badRequest1) - - expect(res1.status).toBe(400) - expect(res1.body).toStrictEqual({ - success: false, - version: res1.body.version, - error: WarningMessage.INVALID_INPUT, - }) - - const badRequest2 = '' - - const res2 = await request(app) - .post(CombinerEndpoint.DOMAIN_QUOTA_STATUS) - .send(badRequest2) - - expect(res2.status).toBe(400) - expect(res2.body).toStrictEqual({ - success: false, - version: res2.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed auth', async () => { - // Create a manipulated request, which will have a bad signature. - const badRequest = await quotaRequest() - badRequest.domain.salt = defined('badSalt') - - const res = await request(app).post(CombinerEndpoint.DOMAIN_QUOTA_STATUS).send(badRequest) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 503 on disabled api', async () => { - const configWithApiDisabled: typeof combinerConfig = JSON.parse( - JSON.stringify(combinerConfig) - ) - configWithApiDisabled.domains.enabled = false - const appWithApiDisabled = startCombiner( - configWithApiDisabled, - getContractKitWithAgent(configWithApiDisabled.blockchain) - ) - - const req = await quotaRequest() - - const res = await request(appWithApiDisabled) - .post(CombinerEndpoint.DOMAIN_QUOTA_STATUS) - .send(req) - - expect(res.status).toBe(503) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - }) - - describe(`${CombinerEndpoint.DOMAIN_SIGN}`, () => { - it('Should respond with 200 on valid request', async () => { - const [req, poprfClient] = await signatureRequest() - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - signature: res.body.signature, - status: { - disabled: false, - counter: 1, - timer: res.body.status.timer, - now: res.body.status.now, - }, - }) - const evaluation = poprfClient.unblindResponse(Buffer.from(res.body.signature, 'base64')) - expect(evaluation.toString('base64')).toEqual(expectedEval) - }) - - for (let i = 1; i <= 3; i++) { - it(`Should respond with 200 on valid request with key version header ${i}`, async () => { - const [req, poprfClient] = await signatureRequest( - undefined, - undefined, - TestUtils.Values.DOMAINS_THRESHOLD_DEV_PUBKEYS[i - 1] - ) - - const res = await request(app) - .post(CombinerEndpoint.DOMAIN_SIGN) - .set(KEY_VERSION_HEADER, i.toString()) - .send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - signature: res.body.signature, - status: { - disabled: false, - counter: 1, - timer: res.body.status.timer, - now: res.body.status.now, - }, - }) - const evaluation = poprfClient.unblindResponse( - Buffer.from(res.body.signature, 'base64') - ) - expect(evaluation.toString('base64')).toEqual(expectedEvals[i - 1]) - }) - } - - it('Should respond with 200 if nonce > domainState', async () => { - const [req, poprfClient] = await signatureRequest(undefined, 2) - - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - signature: res.body.signature, - status: { - disabled: false, - counter: 1, // counter gets incremented, not set to nonce value - timer: res.body.status.timer, - now: res.body.status.now, - }, - }) - const evaluation = poprfClient.unblindResponse(Buffer.from(res.body.signature, 'base64')) - expect(evaluation.toString('base64')).toEqual(expectedEval) - }) - - it('Should respond with 200 on repeated valid requests', async () => { - const [req1, poprfClient] = await signatureRequest() - - const res1 = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(req1) - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual({ - success: true, - version: res1.body.version, - signature: res1.body.signature, - status: { - disabled: false, - counter: 1, - timer: res1.body.status.timer, - now: res1.body.status.now, - }, - }) - const eval1 = poprfClient.unblindResponse(Buffer.from(res1.body.signature, 'base64')) - expect(eval1.toString('base64')).toEqual(expectedEval) - - // submit identical request with nonce set to 1 - req1.options.nonce = defined(1) - req1.options.signature = noString - req1.options.signature = defined( - await wallet.signTypedData(walletAddress, domainRestrictedSignatureRequestEIP712(req1)) - ) - const res2 = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(req1) - - expect(res2.status).toBe(200) - expect(res2.body).toStrictEqual({ - success: true, - version: res2.body.version, - signature: res2.body.signature, - status: { - disabled: false, - counter: 2, - timer: res2.body.status.timer, - now: res2.body.status.now, - }, - }) - const eval2 = poprfClient.unblindResponse(Buffer.from(res1.body.signature, 'base64')) - expect(eval2).toEqual(eval1) - }) - - it('Should respond with 200 on extra request fields', async () => { - const [req, poprfClient] = await signatureRequest() - // @ts-ignore Intentionally adding an extra field to the request type - req.options.extraField = noString - - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - signature: res.body.signature, - status: { - disabled: false, - counter: 1, - timer: res.body.status.timer, - now: res.body.status.now, - }, - }) - const evaluation = poprfClient.unblindResponse(Buffer.from(res.body.signature, 'base64')) - expect(evaluation.toString('base64')).toEqual(expectedEval) - }) - - it('Should respond with 400 on missing request fields', async () => { - const [badRequest, _] = await signatureRequest() - // @ts-ignore Intentionally deleting required field - delete badRequest.domain.version - - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(badRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on unknown domain', async () => { - // Create a requests with an invalid domain identifier. - const [unknownRequest, _] = await signatureRequest() - // @ts-ignore UnknownDomain is (intentionally) not a valid domain identifier. - unknownRequest.domain.name = 'UnknownDomain' - - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(unknownRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on bad encoding', async () => { - const [badRequest1, _] = await signatureRequest() - // @ts-ignore Intentionally not JSON - badRequest1.domain = 'Freddy' - - const res1 = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(badRequest1) - - expect(res1.status).toBe(400) - expect(res1.body).toStrictEqual({ - success: false, - version: res1.body.version, - error: WarningMessage.INVALID_INPUT, - }) - - const badRequest2 = '' - - const res2 = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(badRequest2) - - expect(res2.status).toBe(400) - expect(res2.body).toStrictEqual({ - success: false, - version: res2.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on unsupported key version', async () => { - const [badRequest, _] = await signatureRequest() - - const res = await request(app) - .post(CombinerEndpoint.DOMAIN_SIGN) - .set(KEY_VERSION_HEADER, '4') - .send(badRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_KEY_VERSION_REQUEST, - }) - }) - - it('Should respond with 401 on failed auth', async () => { - // Create a manipulated request, which will have a bad signature. - const [badRequest, _] = await signatureRequest() - badRequest.domain.salt = defined('badSalt') - - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(badRequest) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 401 on invalid nonce', async () => { - // Request must be sent first since nonce check is >= 0 - const [req1, _] = await signatureRequest() - const res1 = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(req1) - - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual({ - success: true, - version: res1.body.version, - signature: res1.body.signature, - status: { - disabled: false, - counter: 1, - timer: res1.body.status.timer, - now: res1.body.status.now, - }, - }) - const res2 = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(req1) - expect(res2.status).toBe(401) - expect(res2.body).toStrictEqual({ - success: false, - version: res2.body.version, - error: WarningMessage.INVALID_NONCE, - }) - }) - - it('Should respond with 429 on out of quota', async () => { - const noQuotaDomain = authenticatedDomain([ - { delay: 0, resetTimer: noBool, batchSize: defined(0), repetitions: defined(0) }, - ]) - const [badRequest, _] = await signatureRequest(noQuotaDomain) - - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(badRequest) - - expect(res.status).toBe(429) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.EXCEEDED_QUOTA, - }) - }) - - it('Should respond with 429 on request too early', async () => { - // This domain won't accept requests until ~10 seconds after test execution - const noQuotaDomain = authenticatedDomain([ - { - delay: Math.floor(Date.now() / 1000) + 10, - resetTimer: noBool, - batchSize: defined(2), - repetitions: defined(1), - }, - ]) - const [badRequest, _] = await signatureRequest(noQuotaDomain) - - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(badRequest) - - expect(res.status).toBe(429) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.EXCEEDED_QUOTA, - }) - }) - - it('Should respond with 429 when requesting a signature from a disabled domain', async () => { - const testDomain = authenticatedDomain() - const resDisable = await request(app) - .post(CombinerEndpoint.DISABLE_DOMAIN) - .send(await disableRequest(testDomain)) - expect(resDisable.status).toBe(200) - expect(resDisable.body).toStrictEqual({ - success: true, - version: resDisable.body.version, - status: { disabled: true, counter: 0, timer: 0, now: resDisable.body.status.now }, - }) - }) - - it('Should respond with 503 on disabled api', async () => { - const configWithApiDisabled: typeof combinerConfig = JSON.parse( - JSON.stringify(combinerConfig) - ) - configWithApiDisabled.domains.enabled = false - const appWithApiDisabled = startCombiner( - configWithApiDisabled, - getContractKitWithAgent(configWithApiDisabled.blockchain) - ) - - const [req, _] = await signatureRequest() - - const res = await request(appWithApiDisabled).post(CombinerEndpoint.DOMAIN_SIGN).send(req) - - expect(res.status).toBe(503) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - }) - }) - - describe('when signers are not operating correctly', () => { - // In this case (1/3 signers are correct), response unblinding is guaranteed to fail - // Testing 2/3 signers is flaky since the combiner sometimes combines two - // correct signatures and returns, and sometimes combines one wrong/one correct - // since it cannot verify the sigs server-side. - describe('when 1/3 signers return correct signatures', () => { - beforeEach(async () => { - // Signer 1 & 2's v1 keys are misconfigured to point to the v3 share - const badKeyProvider1 = new MockKeyProvider( - new Map([[`${DefaultKeyName.DOMAINS}-1`, DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V3]]) - ) - const badKeyProvider2 = new MockKeyProvider( - new Map([[`${DefaultKeyName.DOMAINS}-1`, DOMAINS_THRESHOLD_DEV_PK_SHARE_2_V3]]) - ) - signer1 = startSigner(signerConfig, signerDB1, badKeyProvider1).listen(3001) - signer2 = startSigner(signerConfig, signerDB2, badKeyProvider2).listen(3002) - signer3 = startSigner(signerConfig, signerDB3, keyProvider3).listen(3003) - }) - - describe(`${CombinerEndpoint.DOMAIN_SIGN}`, () => { - it('Should respond with 200 on valid request', async () => { - // Ensure requested keyVersion is one that signer1 does not have - const [req, poprfClient] = await signatureRequest( - undefined, - undefined, - TestUtils.Values.DOMAINS_THRESHOLD_DEV_PUBKEY_V1 - ) - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - signature: res.body.signature, - status: { - disabled: false, - counter: 1, - timer: res.body.status.timer, - now: res.body.status.now, - }, - }) - expect(() => - poprfClient.unblindResponse(Buffer.from(res.body.signature, 'base64')) - ).toThrow(/verification failed/) - }) - }) - }) - - describe('when 2/3 of signers are disabled', () => { - beforeEach(async () => { - const configWithApiDisabled: SignerConfig = JSON.parse(JSON.stringify(signerConfig)) - configWithApiDisabled.api.domains.enabled = false - signer1 = startSigner(signerConfig, signerDB1, keyProvider1).listen(3001) - signer2 = startSigner(configWithApiDisabled, signerDB2, keyProvider2).listen(3002) - signer3 = startSigner(configWithApiDisabled, signerDB3, keyProvider3).listen(3003) - }) - - describe(`${CombinerEndpoint.DISABLE_DOMAIN}`, () => { - it('Should fail to reach threshold of signers on valid request', async () => { - const res = await request(app) - .post(CombinerEndpoint.DISABLE_DOMAIN) - .send(await disableRequest()) - expect(res.status).toBe(503) // majority error code in this case - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: ErrorMessage.THRESHOLD_DISABLE_DOMAIN_FAILURE, - }) - }) - }) - - describe(`${CombinerEndpoint.DOMAIN_QUOTA_STATUS}`, () => { - it('Should fail to reach threshold of signers on valid request', async () => { - const res = await request(app) - .post(CombinerEndpoint.DOMAIN_QUOTA_STATUS) - .send(await quotaRequest()) - expect(res.status).toBe(503) // majority error code in this case - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: ErrorMessage.THRESHOLD_DOMAIN_QUOTA_STATUS_FAILURE, - }) - }) - }) - - describe(`${CombinerEndpoint.DOMAIN_SIGN}`, () => { - it('Should fail to reach threshold of signers on valid request', async () => { - const [req, _] = await signatureRequest() - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(req) - expect(res.status).toBe(503) // majority error code in this case - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES, - }) - }) - }) - }) - - describe('when 1/3 of signers are disabled', () => { - beforeEach(async () => { - const configWithApiDisabled: SignerConfig = JSON.parse(JSON.stringify(signerConfig)) - configWithApiDisabled.api.domains.enabled = false - signer1 = startSigner(signerConfig, signerDB1, keyProvider1).listen(3001) - signer2 = startSigner(signerConfig, signerDB2, keyProvider2).listen(3002) - signer3 = startSigner(configWithApiDisabled, signerDB3, keyProvider3).listen(3003) - }) - - describe(`${CombinerEndpoint.DISABLE_DOMAIN}`, () => { - it('Should respond with 200 on valid request', async () => { - const res = await request(app) - .post(CombinerEndpoint.DISABLE_DOMAIN) - .send(await disableRequest()) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - status: { disabled: true, counter: 0, timer: 0, now: res.body.status.now }, - }) - }) - }) - - describe(`${CombinerEndpoint.DOMAIN_QUOTA_STATUS}`, () => { - it('Should respond with 200 on valid request', async () => { - const res = await request(app) - .post(CombinerEndpoint.DOMAIN_QUOTA_STATUS) - .send(await quotaRequest()) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - status: { disabled: false, counter: 0, timer: 0, now: res.body.status.now }, - }) - }) - }) - - describe(`${CombinerEndpoint.DOMAIN_SIGN}`, () => { - it('Should respond with 200 on valid request', async () => { - const [req, poprfClient] = await signatureRequest() - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - signature: res.body.signature, - status: { - disabled: false, - counter: 1, - timer: res.body.status.timer, - now: res.body.status.now, - }, - }) - const evaluation = poprfClient.unblindResponse( - Buffer.from(res.body.signature, 'base64') - ) - expect(evaluation.toString('base64')).toEqual(expectedEval) - }) - }) - }) - - describe('when signers timeout', () => { - beforeEach(async () => { - const testTimeoutMS = 0 - - const configWithShortTimeout: SignerConfig = JSON.parse(JSON.stringify(signerConfig)) - configWithShortTimeout.timeout = testTimeoutMS - // Test this with all signers timing out to decrease possibility of race conditions - signer1 = startSigner(configWithShortTimeout, signerDB1, keyProvider1).listen(3001) - signer2 = startSigner(configWithShortTimeout, signerDB2, keyProvider2).listen(3002) - signer3 = startSigner(configWithShortTimeout, signerDB3, keyProvider3).listen(3003) - }) - - describe(`${CombinerEndpoint.DISABLE_DOMAIN}`, () => { - it('Should fail to reach threshold of signers on valid request', async () => { - const res = await request(app) - .post(CombinerEndpoint.DISABLE_DOMAIN) - .send(await disableRequest()) - expect(res.status).toBe(500) // majority error code in this case - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: ErrorMessage.THRESHOLD_DISABLE_DOMAIN_FAILURE, - }) - }, 10000) - }) - - describe(`${CombinerEndpoint.DOMAIN_QUOTA_STATUS}`, () => { - it('Should fail to reach threshold of signers on valid request', async () => { - const res = await request(app) - .post(CombinerEndpoint.DOMAIN_QUOTA_STATUS) - .send(await quotaRequest()) - expect(res.status).toBe(500) // majority error code in this case - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: ErrorMessage.THRESHOLD_DOMAIN_QUOTA_STATUS_FAILURE, - }) - }, 10000) - }) - - describe(`${CombinerEndpoint.DOMAIN_SIGN}`, () => { - it('Should fail to reach threshold of signers on valid request', async () => { - const [req, _] = await signatureRequest() - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(req) - expect(res.status).toBe(500) // majority error code in this case - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES, - }) - }, 10000) - }) - }) - }) - }) - - // Ensure the same behavior when a minority of signers can block the threshold. - // On failure, the majority error code should not reflect the abort. - describe('with n=5, t=4', () => { - let keyProvider4: KeyProvider - let keyProvider5: KeyProvider - let signerDB4: Knex - let signerDB5: Knex - let signer4: Server | HttpsServer - let signer5: Server | HttpsServer - - const combinerConfigLargerN: typeof config = JSON.parse(JSON.stringify(combinerConfig)) - combinerConfigLargerN.domains.odisServices.signers = JSON.stringify([ - { - url: 'http://localhost:3001', - fallbackUrl: 'http://localhost:3001/fallback', - }, - { - url: 'http://localhost:3002', - fallbackUrl: 'http://localhost:3002/fallback', - }, - { - url: 'http://localhost:3003', - fallbackUrl: 'http://localhost:3003/fallback', - }, - { - url: 'http://localhost:3004', - fallbackUrl: 'http://localhost:3004/fallback', - }, - { - url: 'http://localhost:3005', - fallbackUrl: 'http://localhost:3005/fallback', - }, - ]) - const DOMAINS_PUBKEY_N5_T4 = - 'gEAedm5Gq+6s/r4ohLrduNmb7IznkYxHQ46+IW0iwEjcjWCi3lkJuItDVa3EXaoBKF4yFAa7wtuX7I8hB3m730XdEpd/77C2GOVGtwDshtCgajSzx7+0zvrnat5QmTkB' - - combinerConfigLargerN.domains.keys.versions = JSON.stringify([ - { - keyVersion: 1, - threshold: 4, - polynomial: - '040000000000000080401e766e46abeeacfebe2884baddb8d99bec8ce7918c47438ebe216d22c048dc8d60a2de5909b88b4355adc45daa01285e321406bbc2db97ec8f210779bbdf45dd12977fefb0b618e546b700ec86d0a06a34b3c7bfb4cefae76ade50993901bbf28cb0b2245e5de0926ce13338440e3b5a378dfe10ba41f61d145d9d5df29ce32abba7562804919101e4803bb47301da265654875d06a0c355b93918ff58efe29c6225d42c2b60c4efbdf7984e3b1ed4e997d53e719aa93fdc10171202460055955ad375e460a181f701a22f543365622c4a5f6ad16fce62e24584b77c23db48312840d0301197c3529cda8b712e01897a2cefab5437f658c59a2a3d880315c3268de1128b333a51d36b2999bf587d25ec82f3695db4c75f9825baf88a460002b11295e74511608041574063faa86f27251a2766861ada6a89bf45454aa4b933992f80622a810b8aead298964f37004ad57215433da765e1d3aae5d9b57ad2d9afcdf77f227e48040ac5701abce7995f94ac0c4c70996333396620e6cf8e00', - pubKey: DOMAINS_PUBKEY_N5_T4, - }, - ]) - - beforeAll(async () => { - keyProvider1 = new MockKeyProvider( - new Map([ - [ - `${DefaultKeyName.DOMAINS}-1`, - '01000000fa9f3c7a0ed050b3b4ab9df241e3e3e2069e36c96369b2bf378d7edd66e37a0e', - ], - ]) - ) - keyProvider2 = new MockKeyProvider( - new Map([ - [ - `${DefaultKeyName.DOMAINS}-1`, - '02000000b03e8d5203edb8b27a9c56185df0ee94e8be45f0c91e116beac68c851cc4ba10', - ], - ]) - ) - keyProvider3 = new MockKeyProvider( - new Map([ - [ - `${DefaultKeyName.DOMAINS}-1`, - '03000000bad95bc039a5418bd57e7f5bad4bf6c19ff85e523462925e226e6ac0a6770005', - ], - ]) - ) - keyProvider4 = new MockKeyProvider( - new Map([ - [ - `${DefaultKeyName.DOMAINS}-1`, - '040000002ffdcc94fd5322e4f3e0d7ba00b591c38f77e584b61b59dd6e62cc6cfed5fd06', - ], - ]) - ) - keyProvider5 = new MockKeyProvider( - new Map([ - [ - `${DefaultKeyName.DOMAINS}-1`, - '05000000243505a19a546f5002511f9527fe03401908cd6427991f69b2380e355fec0d0d', - ], - ]) - ) - app = startCombiner( - combinerConfigLargerN, - getContractKitWithAgent(combinerConfigLargerN.blockchain) - ) - }) - - beforeEach(async () => { - signerDB1 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB2 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB3 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB4 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB5 = await initSignerDatabase(signerConfig, signerMigrationsPath) - - signer1 = startSigner(signerConfig, signerDB1, keyProvider1).listen(3001) - signer2 = startSigner(signerConfig, signerDB2, keyProvider2).listen(3002) - signer3 = startSigner(signerConfig, signerDB3, keyProvider3).listen(3003) - signer4 = startSigner(signerConfig, signerDB4, keyProvider4).listen(3004) - signer5 = startSigner(signerConfig, signerDB5, keyProvider5).listen(3005) - }) - - afterEach(async () => { - await signerDB1?.destroy() - await signerDB2?.destroy() - await signerDB3?.destroy() - await signerDB4?.destroy() - await signerDB5?.destroy() - await serverClose(signer1) - await serverClose(signer2) - await serverClose(signer3) - await serverClose(signer4) - await serverClose(signer5) - }) - - it('Should respond with 200 on valid request', async () => { - const [req, poprfClient] = await signatureRequest(undefined, undefined, DOMAINS_PUBKEY_N5_T4) - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - signature: res.body.signature, - status: { - disabled: false, - counter: 1, - timer: res.body.status.timer, - now: res.body.status.now, - }, - }) - poprfClient.unblindResponse(Buffer.from(res.body.signature, 'base64')) - }) - - // This previously incorrectly returned 502 instead of 429 - it('Should respond with 429 on out of quota', async () => { - const noQuotaDomain = authenticatedDomain([ - { delay: 0, resetTimer: noBool, batchSize: defined(0), repetitions: defined(0) }, - ]) - const [badRequest, _] = await signatureRequest(noQuotaDomain, undefined, DOMAINS_PUBKEY_N5_T4) - - const res = await request(app).post(CombinerEndpoint.DOMAIN_SIGN).send(badRequest) - - expect(res.status).toBe(429) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.EXCEEDED_QUOTA, - }) - }) - }) -}) diff --git a/packages/phone-number-privacy/combiner/test/integration/pnp.test.ts b/packages/phone-number-privacy/combiner/test/integration/pnp.test.ts deleted file mode 100644 index a300adf997..0000000000 --- a/packages/phone-number-privacy/combiner/test/integration/pnp.test.ts +++ /dev/null @@ -1,1308 +0,0 @@ -import { newKit } from '@celo/contractkit' -import { - AuthenticationMethod, - CombinerEndpoint, - DB_TIMEOUT, - ErrorMessage, - FULL_NODE_TIMEOUT_IN_MS, - genSessionID, - KEY_VERSION_HEADER, - PnpQuotaRequest, - PnpQuotaResponseFailure, - PnpQuotaResponseSuccess, - RETRY_COUNT, - RETRY_DELAY_IN_MS, - SignerEndpoint, - SignMessageRequest, - SignMessageResponseFailure, - SignMessageResponseSuccess, - TestUtils, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { initDatabase as initSignerDatabase } from '@celo/phone-number-privacy-signer/dist/common/database/database' -import { - DefaultKeyName, - KeyProvider, -} from '@celo/phone-number-privacy-signer/dist/common/key-management/key-provider-base' -import { MockKeyProvider } from '@celo/phone-number-privacy-signer/dist/common/key-management/mock-key-provider' -import { - SignerConfig, - SupportedDatabase, - SupportedKeystore, -} from '@celo/phone-number-privacy-signer/dist/config' -import { startSigner } from '@celo/phone-number-privacy-signer/dist/server' -import BigNumber from 'bignumber.js' -import threshold_bls from 'blind-threshold-bls' -import { Server } from 'http' -import { Server as HttpsServer } from 'https' -import { Knex } from 'knex' -import request from 'supertest' -import config, { getCombinerVersion } from '../../src/config' -import { startCombiner } from '../../src/server' -import { getBlindedPhoneNumber, serverClose } from '../utils' - -const { - ContractRetrieval, - createMockContractKit, - createMockAccounts, - createMockOdisPayments, - getPnpRequestAuthorization, -} = TestUtils.Utils -const { - PRIVATE_KEY1, - ACCOUNT_ADDRESS1, - mockAccount, - DEK_PRIVATE_KEY, - DEK_PUBLIC_KEY, - PNP_THRESHOLD_DEV_PK_SHARE_1_V1, - PNP_THRESHOLD_DEV_PK_SHARE_1_V2, - PNP_THRESHOLD_DEV_PK_SHARE_1_V3, - PNP_THRESHOLD_DEV_PK_SHARE_2_V1, - PNP_THRESHOLD_DEV_PK_SHARE_2_V2, - PNP_THRESHOLD_DEV_PK_SHARE_2_V3, - PNP_THRESHOLD_DEV_PK_SHARE_3_V1, - PNP_THRESHOLD_DEV_PK_SHARE_3_V2, - PNP_THRESHOLD_DEV_PK_SHARE_3_V3, - ACCOUNT_ADDRESS2, - BLINDING_FACTOR, -} = TestUtils.Values - -jest.setTimeout(20000) - -// create deep copy of config -const combinerConfig: typeof config = JSON.parse(JSON.stringify(config)) -combinerConfig.phoneNumberPrivacy.enabled = true - -const signerConfig: SignerConfig = { - serviceName: 'odis-signer', - server: { - port: undefined, - sslKeyPath: undefined, - sslCertPath: undefined, - }, - quota: { - unverifiedQueryMax: 10, - additionalVerifiedQueryMax: 30, - queryPerTransaction: 2, - // Min balance is .01 cUSD - minDollarBalance: new BigNumber(1e16), - // Min balance is .01 cEUR - minEuroBalance: new BigNumber(1e16), - // Min balance is .005 CELO - minCeloBalance: new BigNumber(5e15), - // Equivalent to 0.001 cUSD/query - queryPriceInCUSD: new BigNumber(0.001), - }, - api: { - domains: { - enabled: false, - }, - phoneNumberPrivacy: { - enabled: true, - }, - }, - blockchain: { - provider: 'https://alfajores-forno.celo-testnet.org', - apiKey: undefined, - }, - db: { - type: SupportedDatabase.Sqlite, - user: '', - password: '', - database: '', - host: 'http://localhost', - port: undefined, - ssl: true, - poolMaxSize: 50, - timeout: DB_TIMEOUT, - }, - keystore: { - type: SupportedKeystore.MOCK_SECRET_MANAGER, - keys: { - phoneNumberPrivacy: { - name: 'phoneNumberPrivacy', - latest: 2, - }, - domains: { - name: 'domains', - latest: 1, - }, - }, - azure: { - clientID: '', - clientSecret: '', - tenant: '', - vaultName: '', - }, - google: { - projectId: '', - }, - aws: { - region: '', - secretKey: '', - }, - }, - timeout: 5000, - test_quota_bypass_percentage: 0, - fullNodeTimeoutMs: FULL_NODE_TIMEOUT_IN_MS, - fullNodeRetryCount: RETRY_COUNT, - fullNodeRetryDelayMs: RETRY_DELAY_IN_MS, - // TODO (alec) make SignerConfig better - shouldMockAccountService: false, - mockDek: '', - mockTotalQuota: 0, - shouldMockRequestService: false, - requestPrunningDays: 0, - requestPrunningAtServerStart: false, - requestPrunningJobCronPattern: '0 0 0 * * *', -} - -const mockOdisPaymentsTotalPaidCUSD = jest.fn() -const mockGetWalletAddress = jest.fn() -const mockGetDataEncryptionKey = jest.fn() - -const mockContractKit = createMockContractKit({ - [ContractRetrieval.getAccounts]: createMockAccounts( - mockGetWalletAddress, - mockGetDataEncryptionKey - ), - [ContractRetrieval.getOdisPayments]: createMockOdisPayments(mockOdisPaymentsTotalPaidCUSD), -}) - -// Mock newKit as opposed to the CK constructor -// Returns an object of type ContractKit that can be passed into the signers + combiner -jest.mock('@celo/contractkit', () => ({ - ...jest.requireActual('@celo/contractkit'), - newKit: jest.fn().mockImplementation(() => mockContractKit), -})) - -describe('pnpService', () => { - let keyProvider1: KeyProvider - let keyProvider2: KeyProvider - let keyProvider3: KeyProvider - let signerDB1: Knex - let signerDB2: Knex - let signerDB3: Knex - let signer1: Server | HttpsServer - let signer2: Server | HttpsServer - let signer3: Server | HttpsServer - let app: any - - // Used by PNP_SIGN tests for various configurations of signers - let userSeed: Uint8Array - let blindedMsgResult: threshold_bls.BlindedMessage - - const signerMigrationsPath = '../signer/dist/common/database/migrations' - const expectedVersion = getCombinerVersion() - - const onChainPaymentsDefault = new BigNumber(1e18) - const expectedTotalQuota = 1000 - - const message = Buffer.from('test message', 'utf8') - - // In current setup, the same mocked kit is used for the combiner and signers - const mockKit = newKit('dummyKit') - - const sendPnpSignRequest = async ( - req: SignMessageRequest, - authorization: string, - app: any, - keyVersionHeader?: string - ) => { - let reqWithHeaders = request(app) - .post(CombinerEndpoint.PNP_SIGN) - .set('Authorization', authorization) - - if (keyVersionHeader) { - reqWithHeaders = reqWithHeaders.set(KEY_VERSION_HEADER, keyVersionHeader) - } - return reqWithHeaders.send(req) - } - - const getSignRequest = (_blindedMsgResult: threshold_bls.BlindedMessage): SignMessageRequest => { - return { - account: ACCOUNT_ADDRESS1, - blindedQueryPhoneNumber: Buffer.from(_blindedMsgResult.message).toString('base64'), - sessionID: genSessionID(), - } - } - - const useQuery = async (performedQueryCount: number, signer: Server | HttpsServer) => { - for (let i = 0; i < performedQueryCount; i++) { - const phoneNumber = '+1' + Math.floor(Math.random() * 10 ** 10) - const blindedNumber = getBlindedPhoneNumber(phoneNumber, BLINDING_FACTOR) - const req = { - account: ACCOUNT_ADDRESS1, - blindedQueryPhoneNumber: blindedNumber, - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - await request(signer) - .post(SignerEndpoint.PNP_SIGN) - .set('Authorization', authorization) - .send(req) - } - } - - const getCombinerQuotaResponse = async ( - req: PnpQuotaRequest, - authorization: string, - _app: any = app - ) => { - const res = await request(_app) - .post(CombinerEndpoint.PNP_QUOTA) - .set('Authorization', authorization) - .send(req) - return res - } - - describe('with n=3, t=2', () => { - const expectedSignatures: string[] = [ - 'xgFMQtcgAMHJAEX/m9B4VFopYtxqPFSw0024sWzRYvQDvnmFqhXOPdnRDfa8WCEA', - 'wUuFV8yFBXGyEzKbyWjBChG6dER264nwjOsqErd/UZieVKE0oDMZcMDG+qObu4QB', - 'PJHqBGavcQG3NGFl3hiR8GymeDNumxbl1DnCJzWz+Ik5yCN2ZpAITBe24RTX0iMA', - ] - const expectedSignature = expectedSignatures[config.phoneNumberPrivacy.keys.currentVersion - 1] - - const expectedUnblindedSigs: string[] = [ - 'lOASnDJNbJBTMYfkbU4fMiK7FcNwSyqZo8iQSM95X8YK+/158be4S1A+jcQsCUYA', - 'QIT7HtHTe/d0Tq40Mf3rpHCT8qY20+8q7ZW9PXHFMWGvwSGhk7l3Pfwnx8YdXomB', - 'XW//DolLzaXYS/gk9WBHfeKy5HKrGjuF/OpCok/i6fprE4AGFH2PjE7zeKTfOQ+A', - ] - const expectedUnblindedSig = - expectedUnblindedSigs[config.phoneNumberPrivacy.keys.currentVersion - 1] - - beforeAll(async () => { - keyProvider1 = new MockKeyProvider( - new Map([ - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, PNP_THRESHOLD_DEV_PK_SHARE_1_V1], - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-2`, PNP_THRESHOLD_DEV_PK_SHARE_1_V2], - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-3`, PNP_THRESHOLD_DEV_PK_SHARE_1_V3], - ]) - ) - keyProvider2 = new MockKeyProvider( - new Map([ - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, PNP_THRESHOLD_DEV_PK_SHARE_2_V1], - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-2`, PNP_THRESHOLD_DEV_PK_SHARE_2_V2], - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-3`, PNP_THRESHOLD_DEV_PK_SHARE_2_V3], - ]) - ) - keyProvider3 = new MockKeyProvider( - new Map([ - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, PNP_THRESHOLD_DEV_PK_SHARE_3_V1], - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-2`, PNP_THRESHOLD_DEV_PK_SHARE_3_V2], - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-3`, PNP_THRESHOLD_DEV_PK_SHARE_3_V3], - ]) - ) - app = startCombiner(combinerConfig, mockKit) - }) - - beforeEach(async () => { - signerDB1 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB2 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB3 = await initSignerDatabase(signerConfig, signerMigrationsPath) - - userSeed = new Uint8Array(32) - for (let i = 0; i < userSeed.length - 1; i++) { - userSeed[i] = i - } - - blindedMsgResult = threshold_bls.blind(message, userSeed) - - mockGetDataEncryptionKey.mockReset().mockReturnValue(DEK_PUBLIC_KEY) - mockGetWalletAddress.mockReset().mockReturnValue(mockAccount) - }) - - afterEach(async () => { - await serverClose(signer1) - await serverClose(signer2) - await serverClose(signer3) - await signerDB1?.destroy() - await signerDB2?.destroy() - await signerDB3?.destroy() - }) - - describe('when signers are operating correctly', () => { - beforeEach(async () => { - signer1 = startSigner(signerConfig, signerDB1, keyProvider1, mockKit).listen(3001) - signer2 = startSigner(signerConfig, signerDB2, keyProvider2, mockKit).listen(3002) - signer3 = startSigner(signerConfig, signerDB3, keyProvider3, mockKit).listen(3003) - }) - - describe(`${CombinerEndpoint.PNP_QUOTA}`, () => { - const totalQuota = 10 - const weiTocusd = new BigNumber(1e18) - beforeAll(async () => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValue( - weiTocusd.multipliedBy(totalQuota).multipliedBy(signerConfig.quota.queryPriceInCUSD) - ) - }) - - const queryCountParams = [ - { signerQueries: [0, 0, 0], expectedQueryCount: 0, expectedWarnings: [] }, - { - signerQueries: [1, 0, 0], - expectedQueryCount: 0, - expectedWarnings: [WarningMessage.SIGNER_RESPONSE_DISCREPANCIES], - }, // does not reach threshold - { - signerQueries: [1, 1, 0], - expectedQueryCount: 1, - expectedWarnings: [WarningMessage.SIGNER_RESPONSE_DISCREPANCIES], - }, // threshold reached - { - signerQueries: [0, 1, 1], - expectedQueryCount: 1, - expectedWarnings: [WarningMessage.SIGNER_RESPONSE_DISCREPANCIES], - }, // order of signers shouldn't matter - { - signerQueries: [1, 4, 9], - expectedQueryCount: 4, - expectedWarnings: [ - WarningMessage.SIGNER_RESPONSE_DISCREPANCIES, - WarningMessage.INCONSISTENT_SIGNER_QUERY_MEASUREMENTS, - ], - }, - ] - queryCountParams.forEach(({ signerQueries, expectedQueryCount, expectedWarnings }) => { - it(`should get ${expectedQueryCount} performedQueryCount given signer responses of ${signerQueries}`, async () => { - await useQuery(signerQueries[0], signer1) - await useQuery(signerQueries[1], signer2) - await useQuery(signerQueries[2], signer3) - - const req = { - account: ACCOUNT_ADDRESS1, - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await getCombinerQuotaResponse(req, authorization) - - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - performedQueryCount: expectedQueryCount, - totalQuota, - - warnings: expectedWarnings, - }) - }) - }) - - it('Should respond with 200 on valid request', async () => { - const req = { - account: ACCOUNT_ADDRESS1, - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await getCombinerQuotaResponse(req, authorization) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - performedQueryCount: 0, - totalQuota, - - warnings: [], - }) - }) - - it('Should respond with 200 on repeated valid requests', async () => { - const req = { - account: ACCOUNT_ADDRESS1, - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res1 = await getCombinerQuotaResponse(req, authorization) - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual({ - success: true, - version: expectedVersion, - performedQueryCount: 0, - totalQuota, - - warnings: [], - }) - const res2 = await getCombinerQuotaResponse(req, authorization) - expect(res2.status).toBe(200) - expect(res2.body).toStrictEqual(res1.body) - }) - - it('Should respond with 200 on extra request fields', async () => { - const req = { - account: ACCOUNT_ADDRESS1, - extraField: 'dummy', - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await getCombinerQuotaResponse(req, authorization) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - performedQueryCount: 0, - totalQuota, - - warnings: [], - }) - }) - - it('Should respond with 200 when authenticated with DEK', async () => { - const req = { - account: ACCOUNT_ADDRESS1, - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - } - const authorization = getPnpRequestAuthorization(req, DEK_PRIVATE_KEY) - const res = await getCombinerQuotaResponse(req, authorization) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - performedQueryCount: 0, - totalQuota, - - warnings: [], - }) - }) - - it('Should respond with a warning when there are slight discrepancies in total quota', async () => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValueOnce( - weiTocusd.multipliedBy(totalQuota + 1).multipliedBy(signerConfig.quota.queryPriceInCUSD) - ) - const req = { - account: ACCOUNT_ADDRESS1, - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await getCombinerQuotaResponse(req, authorization) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - performedQueryCount: 0, - totalQuota, - - warnings: [ - WarningMessage.SIGNER_RESPONSE_DISCREPANCIES, - WarningMessage.INCONSISTENT_SIGNER_QUOTA_MEASUREMENTS + - ', using threshold signer as best guess', - ], - }) - }) - - it('Should respond with 500 when there are large discrepancies in total quota', async () => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValueOnce(weiTocusd.multipliedBy(totalQuota + 15)) - const req = { - account: ACCOUNT_ADDRESS1, - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await getCombinerQuotaResponse(req, authorization) - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.THRESHOLD_PNP_QUOTA_STATUS_FAILURE, - }) - }) - - it('Should respond with 400 on missing request fields', async () => { - // @ts-ignore Intentionally missing required fields - const req: PnpQuotaRequest = {} - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await getCombinerQuotaResponse(req, authorization) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 with invalid address', async () => { - const req = { - account: 'not an address', - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await getCombinerQuotaResponse(req, authorization) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed WALLET_KEY auth', async () => { - // Request from one account, signed by another account - const req = { - account: ACCOUNT_ADDRESS2, - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await getCombinerQuotaResponse(req, authorization) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 401 on failed DEK auth', async () => { - const req = { - account: ACCOUNT_ADDRESS2, - AuthenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await getCombinerQuotaResponse(req, authorization) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - // This previously returned 502 instead of 500 - it('Should respond with 500 when insufficient signer responses', async () => { - await serverClose(signer1) - await serverClose(signer2) - await signerDB1?.destroy() - await signerDB2?.destroy() - - const req = { - account: ACCOUNT_ADDRESS1, - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await getCombinerQuotaResponse(req, authorization) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.THRESHOLD_PNP_QUOTA_STATUS_FAILURE, - }) - }) - - it('Should respond with 503 on disabled api', async () => { - const configWithApiDisabled: typeof combinerConfig = JSON.parse( - JSON.stringify(combinerConfig) - ) - configWithApiDisabled.phoneNumberPrivacy.enabled = false - const appWithApiDisabled = startCombiner(configWithApiDisabled, mockKit) - const req = { - account: ACCOUNT_ADDRESS1, - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await request(appWithApiDisabled) - .post(CombinerEndpoint.PNP_QUOTA) - .set('Authorization', authorization) - .send(req) - expect(res.status).toBe(503) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - }) - - describe(`${CombinerEndpoint.PNP_SIGN}`, () => { - let req: SignMessageRequest - - beforeEach(async () => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValue(onChainPaymentsDefault) - req = getSignRequest(blindedMsgResult) - }) - - it('Should respond with 200 on valid request', async () => { - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedTotalQuota, - - warnings: [], - }) - const unblindedSig = threshold_bls.unblind( - Buffer.from(res.body.signature, 'base64'), - blindedMsgResult.blindingFactor - ) - - expect(Buffer.from(unblindedSig).toString('base64')).toEqual(expectedUnblindedSig) - }) - - for (let i = 1; i <= 3; i++) { - it(`Should respond with 200 on valid request with key version header ${i}`, async () => { - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app, i.toString()) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignatures[i - 1], - performedQueryCount: 1, - totalQuota: expectedTotalQuota, - - warnings: [], - }) - - const unblindedSig = threshold_bls.unblind( - Buffer.from(res.body.signature, 'base64'), - blindedMsgResult.blindingFactor - ) - - expect(Buffer.from(unblindedSig).toString('base64')).toEqual( - expectedUnblindedSigs[i - 1] - ) - }) - } - - it('Should respond with 200 on repeated valid requests', async () => { - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res1 = await sendPnpSignRequest(req, authorization, app) - const expectedResponse: SignMessageResponseSuccess = { - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedTotalQuota, - - warnings: [], - } - - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual(expectedResponse) - - const res2 = await sendPnpSignRequest(req, authorization, app) - expect(res2.status).toBe(200) - // Do not expect performedQueryCount to increase since this is a duplicate request - expect(res2.body).toStrictEqual(expectedResponse) - }) - - it('Should increment performedQueryCount on request from the same account with a new message', async () => { - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res1 = await sendPnpSignRequest(req, authorization, app) - const expectedResponse: SignMessageResponseSuccess = { - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedTotalQuota, - - warnings: [], - } - - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual(expectedResponse) - - // Second request for the same account but with new message - const message2 = Buffer.from('second test message', 'utf8') - const blindedMsg2 = threshold_bls.blind(message2, userSeed) - const req2 = getSignRequest(blindedMsg2) - const authorization2 = getPnpRequestAuthorization(req2, PRIVATE_KEY1) - - // Expect performedQueryCount to increase - expectedResponse.performedQueryCount++ - expectedResponse.signature = - 'PWvuSYIA249x1dx+qzgl6PKSkoulXXE/P4WHJvGmtw77pCRilEWTn3xSp+6JS9+A' - const res2 = await sendPnpSignRequest(req2, authorization2, app) - expect(res2.status).toBe(200) - expect(res2.body).toStrictEqual(expectedResponse) - }) - - it('Should respond with 200 on extra request fields', async () => { - // @ts-ignore Intentionally adding an extra field to the request type - req.extraField = 'dummyString' - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedTotalQuota, - - warnings: [], - }) - }) - - it('Should respond with 200 when authenticated with DEK', async () => { - req.authenticationMethod = AuthenticationMethod.ENCRYPTION_KEY - const authorization = getPnpRequestAuthorization(req, DEK_PRIVATE_KEY) - const res = await sendPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedTotalQuota, - - warnings: [], - }) - }) - - it('Should respond with 200 on invalid key version', async () => { - req.authenticationMethod = AuthenticationMethod.ENCRYPTION_KEY - const authorization = getPnpRequestAuthorization(req, DEK_PRIVATE_KEY) - const res = await sendPnpSignRequest(req, authorization, app, 'invalid') - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedTotalQuota, - - warnings: [], - }) - }) - - it('Should get the same unblinded signatures from the same message (different seed)', async () => { - const authorization1 = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res1 = await sendPnpSignRequest(req, authorization1, app) - - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedTotalQuota, - - warnings: [], - }) - - const secondUserSeed = new Uint8Array(userSeed) - secondUserSeed[0]++ - // Ensure request is identical except for blinded message - const req2 = { ...req } - const blindedMsgResult2 = threshold_bls.blind(message, secondUserSeed) - req2.blindedQueryPhoneNumber = Buffer.from(blindedMsgResult2.message).toString('base64') - - // Sanity check - expect(req2.blindedQueryPhoneNumber).not.toEqual(req.blindedQueryPhoneNumber) - - const authorization2 = getPnpRequestAuthorization(req2, PRIVATE_KEY1) - const res2 = await sendPnpSignRequest(req2, authorization2, app) - expect(res2.status).toBe(200) - const unblindedSig1 = threshold_bls.unblind( - Buffer.from(res1.body.signature, 'base64'), - blindedMsgResult.blindingFactor - ) - const unblindedSig2 = threshold_bls.unblind( - Buffer.from(res2.body.signature, 'base64'), - blindedMsgResult2.blindingFactor - ) - expect(Buffer.from(unblindedSig1).toString('base64')).toEqual(expectedUnblindedSig) - expect(unblindedSig1).toEqual(unblindedSig2) - }) - - it('Should respond with 400 on missing request fields', async () => { - // @ts-ignore Intentionally deleting required field - delete req.account - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on unsupported key version', async () => { - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app, '4') - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_KEY_VERSION_REQUEST, - }) - }) - - it('Should respond with 401 on failed WALLET_KEY auth', async () => { - req.account = mockAccount - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 401 on failed DEK auth', async () => { - req.account = mockAccount - req.authenticationMethod = AuthenticationMethod.ENCRYPTION_KEY - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(req, differentPk) - const res = await sendPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 403 on out of quota', async () => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValue(new BigNumber(0)) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(403) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.EXCEEDED_QUOTA, - }) - }) - - it('Should respond with 503 on disabled api', async () => { - const configWithApiDisabled: typeof combinerConfig = JSON.parse( - JSON.stringify(combinerConfig) - ) - configWithApiDisabled.phoneNumberPrivacy.enabled = false - const appWithApiDisabled = startCombiner(configWithApiDisabled, mockKit) - - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, appWithApiDisabled) - - expect(res.status).toBe(503) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - - describe('functionality in case of errors', () => { - it('Should return 401 on failure to fetch DEK', async () => { - mockGetDataEncryptionKey.mockImplementation(() => { - throw new Error() - }) - - req.authenticationMethod = AuthenticationMethod.ENCRYPTION_KEY - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - - const combinerConfigWithFailOpenDisabled: typeof combinerConfig = JSON.parse( - JSON.stringify(combinerConfig) - ) - const appWithFailOpenDisabled = startCombiner( - combinerConfigWithFailOpenDisabled, - mockKit - ) - const res = await sendPnpSignRequest(req, authorization, appWithFailOpenDisabled) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - }) - }) - }) - - // For testing combiner code paths when signers do not behave as expected - describe('when signers are not operating correctly', () => { - beforeEach(() => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValue(onChainPaymentsDefault) - }) - - describe('when 2/3 signers return correct signatures', () => { - beforeEach(async () => { - const badBlsShare1 = - '000000002e50aa714ef6b865b5de89c56969ef9f8f27b6b0a6d157c9cc01c574ac9df604' - - const badKeyProvider1 = new MockKeyProvider( - new Map([[`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, badBlsShare1]]) - ) - signer1 = startSigner(signerConfig, signerDB1, badKeyProvider1, mockKit).listen(3001) - signer2 = startSigner(signerConfig, signerDB2, keyProvider2, mockKit).listen(3002) - signer3 = startSigner(signerConfig, signerDB3, keyProvider3, mockKit).listen(3003) - }) - - describe(`${CombinerEndpoint.PNP_SIGN}`, () => { - it('Should respond with 200 on valid request', async () => { - const req = getSignRequest(blindedMsgResult) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedTotalQuota, - - warnings: [], - }) - const unblindedSig = threshold_bls.unblind( - Buffer.from(res.body.signature, 'base64'), - blindedMsgResult.blindingFactor - ) - expect(Buffer.from(unblindedSig).toString('base64')).toEqual(expectedUnblindedSig) - }) - }) - }) - - describe('when 1/3 signers return correct signatures', () => { - beforeEach(async () => { - const badBlsShare1 = - '000000002e50aa714ef6b865b5de89c56969ef9f8f27b6b0a6d157c9cc01c574ac9df604' - const badBlsShare2 = - '01000000b8f0ef841dcf8d7bd1da5e8025e47d729eb67f513335784183b8fa227a0b9a0b' - - const badKeyProvider1 = new MockKeyProvider( - new Map([[`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, badBlsShare1]]) - ) - - const badKeyProvider2 = new MockKeyProvider( - new Map([[`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, badBlsShare2]]) - ) - - signer1 = startSigner(signerConfig, signerDB1, keyProvider1, mockKit).listen(3001) - signer2 = startSigner(signerConfig, signerDB2, badKeyProvider1, mockKit).listen(3002) - signer3 = startSigner(signerConfig, signerDB3, badKeyProvider2, mockKit).listen(3003) - }) - - describe(`${CombinerEndpoint.PNP_SIGN}`, () => { - it('Should respond with 500 even if request is valid', async () => { - const req = getSignRequest(blindedMsgResult) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES, - }) - }) - }) - }) - - describe('when 2/3 of signers are disabled', () => { - beforeEach(async () => { - const configWithApiDisabled: SignerConfig = JSON.parse(JSON.stringify(signerConfig)) - configWithApiDisabled.api.phoneNumberPrivacy.enabled = false - signer1 = startSigner(signerConfig, signerDB1, keyProvider1, mockKit).listen(3001) - signer2 = startSigner(configWithApiDisabled, signerDB2, keyProvider2, mockKit).listen( - 3002 - ) - signer3 = startSigner(configWithApiDisabled, signerDB3, keyProvider3, mockKit).listen( - 3003 - ) - }) - - describe(`${CombinerEndpoint.PNP_QUOTA}`, () => { - it('Should fail to reach threshold of signers on valid request', async () => { - const req = { - account: ACCOUNT_ADDRESS1, - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await getCombinerQuotaResponse(req, authorization) - expect(res.status).toBe(503) // majority error code in this case - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.THRESHOLD_PNP_QUOTA_STATUS_FAILURE, - }) - }) - }) - - describe(`${CombinerEndpoint.PNP_SIGN}`, () => { - it('Should fail to reach threshold of signers on valid request', async () => { - const req = getSignRequest(blindedMsgResult) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(503) // majority error code in this case - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES, - }) - }) - }) - }) - - describe('when 1/3 of signers are disabled', () => { - beforeEach(async () => { - const configWithApiDisabled: SignerConfig = JSON.parse(JSON.stringify(signerConfig)) - configWithApiDisabled.api.phoneNumberPrivacy.enabled = false - signer1 = startSigner(signerConfig, signerDB1, keyProvider1, mockKit).listen(3001) - signer2 = startSigner(signerConfig, signerDB2, keyProvider2, mockKit).listen(3002) - signer3 = startSigner(configWithApiDisabled, signerDB3, keyProvider3, mockKit).listen( - 3003 - ) - }) - - describe(`${CombinerEndpoint.PNP_QUOTA}`, () => { - it('Should respond with 200 on valid request', async () => { - const req = { - account: ACCOUNT_ADDRESS1, - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await getCombinerQuotaResponse(req, authorization) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - performedQueryCount: 0, - totalQuota: expectedTotalQuota, - - warnings: [], - }) - }) - }) - - describe(`${CombinerEndpoint.PNP_SIGN}`, () => { - it('Should respond with 200 on valid request', async () => { - const req = getSignRequest(blindedMsgResult) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedTotalQuota, - - warnings: [], - }) - }) - }) - }) - - describe('when signers timeout', () => { - beforeEach(async () => { - const testTimeoutMS = 0 - - const configWithShortTimeout: SignerConfig = JSON.parse(JSON.stringify(signerConfig)) - configWithShortTimeout.timeout = testTimeoutMS - // Test this with all signers timing out to decrease possibility of race conditions - signer1 = startSigner(configWithShortTimeout, signerDB1, keyProvider1, mockKit).listen( - 3001 - ) - signer2 = startSigner(configWithShortTimeout, signerDB2, keyProvider2, mockKit).listen( - 3002 - ) - signer3 = startSigner(configWithShortTimeout, signerDB3, keyProvider3, mockKit).listen( - 3003 - ) - }) - - describe(`${CombinerEndpoint.PNP_QUOTA}`, () => { - it('Should fail to reach threshold of signers on valid request', async () => { - const req = { - account: ACCOUNT_ADDRESS1, - } - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await getCombinerQuotaResponse(req, authorization) - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.THRESHOLD_PNP_QUOTA_STATUS_FAILURE, - }) - }) - }) - - describe(`${CombinerEndpoint.PNP_SIGN}`, () => { - it('Should fail to reach threshold of signers on valid request', async () => { - const req = getSignRequest(blindedMsgResult) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app) - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES, - }) - }) - }) - }) - }) - }) - - // Ensure the same behavior when a minority of signers can block the threshold. - // On failure, the majority error code should not reflect the abort. - describe('with n=5, t=4', () => { - let keyProvider4: KeyProvider - let keyProvider5: KeyProvider - let signerDB4: Knex - let signerDB5: Knex - let signer4: Server | HttpsServer - let signer5: Server | HttpsServer - - const combinerConfigLargerN: typeof config = JSON.parse(JSON.stringify(combinerConfig)) - combinerConfigLargerN.phoneNumberPrivacy.odisServices.signers = JSON.stringify([ - { - url: 'http://localhost:3001', - fallbackUrl: 'http://localhost:3001/fallback', - }, - { - url: 'http://localhost:3002', - fallbackUrl: 'http://localhost:3002/fallback', - }, - { - url: 'http://localhost:3003', - fallbackUrl: 'http://localhost:3003/fallback', - }, - { - url: 'http://localhost:3004', - fallbackUrl: 'http://localhost:3004/fallback', - }, - { - url: 'http://localhost:3005', - fallbackUrl: 'http://localhost:3005/fallback', - }, - ]) - combinerConfigLargerN.phoneNumberPrivacy.keys.versions = JSON.stringify([ - { - keyVersion: 1, - threshold: 4, - polynomial: - '04000000000000007e196818fb4a5677ab97ef04a8b6e188e253d822c0689e37626fe9690d3a60283e74f2c38ec768f32870d73c7e11ff005ad65aa45707922dfc78d1fd54d64200da22a87d82b93783e2f9ee83ec290e25951c0dac2fb856871eba991731367a80b5f92e54b90901594c5e4d56beb15c44a437e78b90eb01bd4770b9c130feaf42c68d28d4e51415949d692936d3689e000f192e4fdcb03d45d1ffcd3615132046a3c8400e30cecaedf8d9bf275ead903e06ef0552a8326159f5361f8c8d16208197367a3115d3f15651082337e125005814a3f94c307e2205864803cc45dbb1b7e11738edec1d0630973830d0a74d0e0113c6ab677f087fb919728b8e1cb4f0004c6b59b4dcf28be7b4b9a5e9522e216b4d70278eff131717ff121b4203a2668093c54c6287cf9b09dd611627872f40f018f7e5a63eed5c94ead63fcd59515b1b8948482a5b7bdf07f91014d0097bba009316a8219c2074d16de09d557c2e7109112ade0d3f68248df7acfbbc4891acbb313d20021be70664d7a114a7fa6d9e01', - pubKey: - 'fhloGPtKVnerl+8EqLbhiOJT2CLAaJ43Ym/paQ06YCg+dPLDjsdo8yhw1zx+Ef8AWtZapFcHki38eNH9VNZCANoiqH2CuTeD4vnug+wpDiWVHA2sL7hWhx66mRcxNnqA', - }, - ]) - - beforeAll(async () => { - keyProvider1 = new MockKeyProvider( - new Map([ - [ - `${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, - '00000000a49c8a293839ccb24bbcc7b833b0d57fe2f6087d33271750e7d6cf40897f520c', - ], - ]) - ) - keyProvider2 = new MockKeyProvider( - new Map([ - [ - `${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, - '01000000c0866754b43a0e7c6f86c6732c1bc1bc1900f71a0ccab81fcd4048c5ff2edb02', - ], - ]) - ) - keyProvider3 = new MockKeyProvider( - new Map([ - [ - `${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, - '02000000c24271a9dd0827e2939e5afbd5cd1c6705fa40d6e962fb288bbc7201921efa10', - ], - ]) - ) - keyProvider4 = new MockKeyProvider( - new Map([ - [ - `${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, - '030000006320e2a99d4ce6a491a6354feda06051966a056dffbb0e7c8431b246d863ac09', - ], - ]) - ) - keyProvider5 = new MockKeyProvider( - new Map([ - [ - `${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, - '04000000606ff4d6ddae61ac454009af2a49aeb4c297410ef9d3f3ab751c1c4fe5a99c0a', - ], - ]) - ) - app = startCombiner(combinerConfigLargerN, mockKit) - }) - - let req: SignMessageRequest - - beforeEach(async () => { - signerDB1 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB2 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB3 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB4 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB5 = await initSignerDatabase(signerConfig, signerMigrationsPath) - - signer1 = startSigner(signerConfig, signerDB1, keyProvider1, mockKit).listen(3001) - signer2 = startSigner(signerConfig, signerDB2, keyProvider2, mockKit).listen(3002) - signer3 = startSigner(signerConfig, signerDB3, keyProvider3, mockKit).listen(3003) - signer4 = startSigner(signerConfig, signerDB4, keyProvider4, mockKit).listen(3004) - signer5 = startSigner(signerConfig, signerDB5, keyProvider5, mockKit).listen(3005) - - userSeed = new Uint8Array(32) - for (let i = 0; i < userSeed.length - 1; i++) { - userSeed[i] = i - } - - blindedMsgResult = threshold_bls.blind(message, userSeed) - req = getSignRequest(blindedMsgResult) - }) - - afterEach(async () => { - await serverClose(signer1) - await serverClose(signer2) - await serverClose(signer3) - await serverClose(signer4) - await serverClose(signer5) - await signerDB1?.destroy() - await signerDB2?.destroy() - await signerDB3?.destroy() - await signerDB4?.destroy() - await signerDB5?.destroy() - }) - - it('Should respond with 200 on valid request', async () => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValue(onChainPaymentsDefault) - - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: res.body.signature, - performedQueryCount: 1, - totalQuota: expectedTotalQuota, - warnings: [], - }) - threshold_bls.unblind( - Buffer.from(res.body.signature, 'base64'), - blindedMsgResult.blindingFactor - ) - }) - - it('Should respond with 403 on out of quota', async () => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValue(new BigNumber(0)) - - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(403) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.EXCEEDED_QUOTA, - }) - }) - }) -}) diff --git a/packages/phone-number-privacy/combiner/test/unit/bls-signature.test.ts b/packages/phone-number-privacy/combiner/test/unit/bls-signature.test.ts deleted file mode 100644 index 88a5cd3bb5..0000000000 --- a/packages/phone-number-privacy/combiner/test/unit/bls-signature.test.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { KeyVersionInfo, rootLogger } from '@celo/phone-number-privacy-common' -import threshold_bls from 'blind-threshold-bls' -import { BLSCryptographyClient } from '../../src/common/crypto-clients/bls-crypto-client' -import { ServicePartialSignature } from '../../src/common/crypto-clients/crypto-client' -import config from '../../src/config' - -const PUBLIC_KEY = - '8VZ0ZBPBjeRaH2nE+itNKiL/wKl38foK74MniCCIxvpA/9AfE1Uy7qbGGRyiKj8AAeiFpSaMzi7Flfe/Tj/qCWM8LMgQGR+eTvt7yiYsyKIVpGMJYVyzchEtPwFZyRyA' -const PUBLIC_POLYNOMIAL = - '0300000000000000f156746413c18de45a1f69c4fa2b4d2a22ffc0a977f1fa0aef8327882088c6fa40ffd01f135532eea6c6191ca22a3f0001e885a5268cce2ec595f7bf4e3fea09633c2cc810191f9e4efb7bca262cc8a215a46309615cb372112d3f0159c91c80ececfb0ecd57116e44c7580b57fe7c3f0f566d65f789f041b9febd83d3497e4c430af250cf8ac135f4782d283f3dd5009cf6e8de23a35be1cc21a8504ee2e3757a36f6c9813137d0f6b8aa75febc5ee77435cfd4280de80647670a60683e9481f091088a9940142b31d42ed3981dd548910fa41364f589c93c87bf62725468779a1442785600c08efbeff391f84e3200560dcd95d055998f4ef803a820356ef5b756cc75a98286bd21b5675cfe2db9bac0bcee64dc94c435d92aa5fbfa118680' -const SIGNATURES = [ - 'MAAAAAAAAADkHsKIX91BuKRjNgsJR81otwGGln4HuguYe4QkZoInFwNIiU9QglFZeLpJmNEysIAAAAAA', - 'MAAAAAAAAABqscf+GUMQD5I8SJW+zzZKuo83gyRZs/RUR7zePSDx4ZtewOGEc/VThpUpqgM5mAEBAAAA', - 'MAAAAAAAAABH006sJMay5D4OtOHDdQh3W8gX7yafeyMSGJzba7RhBAWatCEztthuQ6gSEOYTYQECAAAA', - 'MAAAAAAAAAAhzTl/S+mldhE+5F5rt+2XKJQsNtELZeo+aoHjhsVVdw8Ofk1ZRr9EUZbvVKetNYADAAAA', -] -const COMBINED_SIGNATURE = '16RcENpbLgq5pIkcPWdgnMofeLqSyuUVin9h4jof9/I8GRsmt5iRxjWAkpftKPWA' -const INVALID_SIGNATURE = - 'MAAAAAAAAACanrA73tApLu+j569ICcXrEBRLi4czWJtInJPSUpoZUOVDc1667hvMq1ESncFzlgEHAAAA' - -const keyVersionInfo: KeyVersionInfo = { - keyVersion: 1, - threshold: 3, - polynomial: PUBLIC_POLYNOMIAL, - pubKey: PUBLIC_KEY, -} - -config.phoneNumberPrivacy.keys = { - currentVersion: keyVersionInfo.keyVersion, - versions: JSON.stringify([keyVersionInfo]), -} - -describe(`BLS service computes signature`, () => { - it('provides blinded signature', async () => { - const signatures: ServicePartialSignature[] = [ - { - url: 'url1', - signature: SIGNATURES[0], - }, - { - url: 'url2', - signature: SIGNATURES[1], - }, - { - url: 'url3', - signature: SIGNATURES[2], - }, - { - url: 'url4', - signature: SIGNATURES[3], - }, - ] - - const message = Buffer.from('hello world') - const userSeed = new Uint8Array(32) - for (let i = 0; i < userSeed.length - 1; i++) { - userSeed[i] = i - } - - const blindedMsgResult = threshold_bls.blind(message, userSeed) - const blindedMsg = Buffer.from(blindedMsgResult.message).toString('base64') - - const blsCryptoClient = new BLSCryptographyClient(keyVersionInfo) - for (let i = 0; i < signatures.length; i++) { - blsCryptoClient.addSignature(signatures[i]) - if (i >= 2) { - expect(blsCryptoClient.hasSufficientSignatures()).toBeTruthy() - } else { - expect(blsCryptoClient.hasSufficientSignatures()).toBeFalsy() - } - } - - const actual = blsCryptoClient.combineBlindedSignatureShares( - blindedMsg, - rootLogger(config.serviceName) - ) - expect(actual).toEqual(COMBINED_SIGNATURE) - - const unblindedSignedMessage = threshold_bls.unblind( - Buffer.from(actual, 'base64'), - blindedMsgResult.blindingFactor - ) - const publicKey = Buffer.from(PUBLIC_KEY, 'base64') - expect(threshold_bls.verify(publicKey, message, unblindedSignedMessage)) - }) - it('provides blinded signature given one failure if still above threshold', async () => { - const signatures: ServicePartialSignature[] = [ - { - url: 'url1', - signature: SIGNATURES[0], - }, - { - url: 'url2', - signature: 'X', // This input causes signature combination to fail - }, - { - url: 'url3', - signature: SIGNATURES[2], - }, - { - url: 'url4', - signature: SIGNATURES[3], - }, - ] - - const message = Buffer.from('hello world') - const userSeed = new Uint8Array(32) - for (let i = 0; i < userSeed.length - 1; i++) { - userSeed[i] = i - } - - const blindedMsgResult = threshold_bls.blind(message, userSeed) - const blindedMsg = Buffer.from(blindedMsgResult.message).toString('base64') - - const blsCryptoClient = new BLSCryptographyClient(keyVersionInfo) - signatures.forEach(async (signature) => { - blsCryptoClient.addSignature(signature) - }) - const actual = blsCryptoClient.combineBlindedSignatureShares( - blindedMsg, - rootLogger(config.serviceName) - ) - expect(actual).toEqual(COMBINED_SIGNATURE) - - const unblindedSignedMessage = threshold_bls.unblind( - Buffer.from(actual, 'base64'), - blindedMsgResult.blindingFactor - ) - const publicKey = Buffer.from(PUBLIC_KEY, 'base64') - expect(threshold_bls.verify(publicKey, message, unblindedSignedMessage)) - }) - it('throws error if does not meet threshold signatures', async () => { - const signatures: ServicePartialSignature[] = [ - { - url: 'url1', - signature: SIGNATURES[0], - }, - { - url: 'url2', - signature: 'X', - }, - { - url: 'url3', - signature: 'X', - }, - { - url: 'url4', - signature: SIGNATURES[3], - }, - ] - - const message = Buffer.from('hello world') - const userSeed = new Uint8Array(32) - for (let i = 0; i < userSeed.length - 1; i++) { - userSeed[i] = i - } - - const blindedMsgResult = threshold_bls.blind(message, userSeed) - const blindedMsg = Buffer.from(blindedMsgResult.message).toString('base64') - - const blsCryptoClient = new BLSCryptographyClient(keyVersionInfo) - signatures.forEach(async (signature) => { - blsCryptoClient.addSignature(signature) - }) - try { - blsCryptoClient.combineBlindedSignatureShares(blindedMsg, rootLogger(config.serviceName)) - throw new Error('Expected failure with missing signatures') - } catch (e: any) { - expect(e.message.includes('Not enough partial signatures')).toBeTruthy() - } - }) - it('throws error if signature cannot be combined, but can recover from failure with sufficient signatures', async () => { - const signatures: ServicePartialSignature[] = [ - { - url: 'url1', - signature: SIGNATURES[0], - }, - { - url: 'url2', - signature: 'X', // This input causes signature combination to fail - }, - { - url: 'url3', - signature: SIGNATURES[2], - }, - { - url: 'url4', - signature: SIGNATURES[3], - }, - ] - - const message = Buffer.from('hello world') - const userSeed = new Uint8Array(32) - for (let i = 0; i < userSeed.length - 1; i++) { - userSeed[i] = i - } - - const blindedMsgResult = threshold_bls.blind(message, userSeed) - const blindedMsg = Buffer.from(blindedMsgResult.message).toString('base64') - - const blsCryptoClient = new BLSCryptographyClient(keyVersionInfo) - // Add sigs one-by-one and verify intermediary states - blsCryptoClient.addSignature(signatures[0]) - expect(blsCryptoClient.hasSufficientSignatures()).toBeFalsy() - blsCryptoClient.addSignature(signatures[1]) - expect(blsCryptoClient.hasSufficientSignatures()).toBeFalsy() - blsCryptoClient.addSignature(signatures[2]) - expect(blsCryptoClient.hasSufficientSignatures()).toBeTruthy() - // Should fail since 1/3 sigs are invalid - try { - blsCryptoClient.combineBlindedSignatureShares(blindedMsg, rootLogger(config.serviceName)) - } catch (e: any) { - expect(e.message.includes('Not enough partial signatures')).toBeTruthy() - } - // Should be false, now that the invalid signature has been removed - expect(blsCryptoClient.hasSufficientSignatures()).toBeFalsy() - - blsCryptoClient.addSignature(signatures[3]) - expect(blsCryptoClient.hasSufficientSignatures()).toBeTruthy() - const actual = blsCryptoClient.combineBlindedSignatureShares( - blindedMsg, - rootLogger(config.serviceName) - ) - expect(actual).toEqual(COMBINED_SIGNATURE) - - const unblindedSignedMessage = threshold_bls.unblind( - Buffer.from(actual, 'base64'), - blindedMsgResult.blindingFactor - ) - const publicKey = Buffer.from(PUBLIC_KEY, 'base64') - expect(threshold_bls.verify(publicKey, message, unblindedSignedMessage)) - }) - it('throws error if combined signature is invalid, and can recover from failure with sufficient valid partial signatures', async () => { - const signatures: ServicePartialSignature[] = [ - { - url: 'url1', - signature: SIGNATURES[0], - }, - { - url: 'url2', - signature: SIGNATURES[1], - }, - { - url: 'url3', - signature: INVALID_SIGNATURE, // Combination will succeed but verification will fail. - }, - { - url: 'url4', - signature: SIGNATURES[3], - }, - ] - - const message = Buffer.from('hello world') - const userSeed = new Uint8Array(32) - for (let i = 0; i < userSeed.length - 1; i++) { - userSeed[i] = i - } - - const blindedMsgResult = threshold_bls.blind(message, userSeed) - const blindedMsg = Buffer.from(blindedMsgResult.message).toString('base64') - - const blsCryptoClient = new BLSCryptographyClient(keyVersionInfo) - // Add sigs one-by-one and verify intermediary states - blsCryptoClient.addSignature(signatures[0]) - expect(blsCryptoClient.hasSufficientSignatures()).toBeFalsy() - blsCryptoClient.addSignature(signatures[1]) - expect(blsCryptoClient.hasSufficientSignatures()).toBeFalsy() - blsCryptoClient.addSignature(signatures[2]) - expect(blsCryptoClient.hasSufficientSignatures()).toBeTruthy() - // Should fail since signature from url3 was generated with the wrong key version - try { - blsCryptoClient.combineBlindedSignatureShares(blindedMsg, rootLogger(config.serviceName)) - } catch (e: any) { - expect(e.message.includes('Not enough partial signatures')).toBeTruthy() - } - - // Should be false, now that the invalid partial signature has been removed - expect(blsCryptoClient.hasSufficientSignatures()).toBeFalsy() - - blsCryptoClient.addSignature(signatures[3]) - expect(blsCryptoClient.hasSufficientSignatures()).toBeTruthy() - const actual = blsCryptoClient.combineBlindedSignatureShares( - blindedMsg, - rootLogger(config.serviceName) - ) - expect(actual).toEqual(COMBINED_SIGNATURE) - - const unblindedSignedMessage = threshold_bls.unblind( - Buffer.from(actual, 'base64'), - blindedMsgResult.blindingFactor - ) - const publicKey = Buffer.from(PUBLIC_KEY, 'base64') - expect(threshold_bls.verify(publicKey, message, unblindedSignedMessage)) - }) -}) diff --git a/packages/phone-number-privacy/combiner/test/unit/domain-response-logger.test.ts b/packages/phone-number-privacy/combiner/test/unit/domain-response-logger.test.ts deleted file mode 100644 index 7041d1c36c..0000000000 --- a/packages/phone-number-privacy/combiner/test/unit/domain-response-logger.test.ts +++ /dev/null @@ -1,327 +0,0 @@ -import { - DisableDomainRequest, - DomainQuotaStatusRequest, - DomainRestrictedSignatureRequest, - OdisResponse, - rootLogger, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { getSignerVersion } from '@celo/phone-number-privacy-signer/src/config' - -import config from '../../src/config' -import { logDomainResponseDiscrepancies } from '../../src/domain/services/log-responses' - -describe('domain response logger', () => { - const url = 'test signer url' - - const logger = rootLogger(config.serviceName) - - const version = getSignerVersion() - const counter = 1 - const disabled = false - const timer = 10000 - - const testCases: { - it: string - responses: OdisResponse< - DomainRestrictedSignatureRequest | DomainQuotaStatusRequest | DisableDomainRequest - >[] - expectedLogs: { - params: string | any[] - level: 'info' | 'debug' | 'warn' | 'error' - }[] - }[] = [ - { - it: 'should log correctly when no responses provided', - responses: [], - expectedLogs: [ - { - params: ['No successful signer responses found!'], - level: 'warn', - }, - ], - }, - { - it: 'should log correctly when all the responses are the same (except for now field)', - responses: [ - { success: true, version, status: { counter, timer, disabled, now: Date.now() } }, - { success: true, version, status: { counter, timer, disabled, now: Date.now() } }, - { success: true, version, status: { counter, timer, disabled, now: Date.now() } }, - ], - expectedLogs: [], - }, - { - it: 'should log correctly when there is a discrepency in version field', - responses: [ - { success: true, version, status: { counter, timer, disabled, now: Date.now() } }, - { success: true, version, status: { counter, timer, disabled, now: Date.now() } }, - { - success: true, - version: 'differentVersion', - status: { counter, timer, disabled, now: Date.now() }, - }, - ], - expectedLogs: [ - { - params: [ - { - parsedResponses: [ - { - signerUrl: url, - values: { - counter, - disabled, - timer, - version, - }, - }, - { - signerUrl: url, - values: { - counter, - disabled, - timer, - version, - }, - }, - { - signerUrl: url, - values: { - counter, - disabled, - timer, - version: 'differentVersion', - }, - }, - ], - }, - WarningMessage.SIGNER_RESPONSE_DISCREPANCIES, - ], - level: 'warn', - }, - ], - }, - { - it: 'should log correctly when there is a discrepency in counter field', - responses: [ - { success: true, version, status: { counter, timer, disabled, now: Date.now() } }, - { success: true, version, status: { counter, timer, disabled, now: Date.now() } }, - { - success: true, - version, - status: { counter: counter + 1, timer, disabled, now: Date.now() }, - }, - ], - expectedLogs: [ - { - params: [ - { - parsedResponses: [ - { - signerUrl: url, - values: { - counter, - disabled, - timer, - version, - }, - }, - { - signerUrl: url, - values: { - counter, - disabled, - timer, - version, - }, - }, - { - signerUrl: url, - values: { - counter: counter + 1, - disabled, - timer, - version, - }, - }, - ], - }, - WarningMessage.SIGNER_RESPONSE_DISCREPANCIES, - ], - level: 'warn', - }, - ], - }, - { - it: 'should log correctly when there is a discrepency in disabled field', - responses: [ - { success: true, version, status: { counter, timer, disabled, now: Date.now() } }, - { success: true, version, status: { counter, timer, disabled, now: Date.now() } }, - { success: true, version, status: { counter, timer, disabled: true, now: Date.now() } }, - ], - expectedLogs: [ - { - params: [ - { - parsedResponses: [ - { - signerUrl: url, - values: { - counter, - disabled, - timer, - version, - }, - }, - { - signerUrl: url, - values: { - counter, - disabled, - timer, - version, - }, - }, - { - signerUrl: url, - values: { - counter, - disabled: true, - timer, - version, - }, - }, - ], - }, - WarningMessage.SIGNER_RESPONSE_DISCREPANCIES, - ], - level: 'warn', - }, - { - params: [ - { - parsedResponses: [ - { - signerUrl: url, - values: { - counter, - disabled, - timer, - version, - }, - }, - { - signerUrl: url, - values: { - counter, - disabled, - timer, - version, - }, - }, - { - signerUrl: url, - values: { - counter, - disabled: true, - timer, - version, - }, - }, - ], - }, - WarningMessage.INCONSISTENT_SIGNER_DOMAIN_DISABLED_STATES, - ], - level: 'error', - }, - ], - }, - { - it: 'should log correctly when there is a discrepency in timer field', - responses: [ - { success: true, version, status: { counter, timer, disabled, now: Date.now() } }, - { success: true, version, status: { counter, timer, disabled, now: Date.now() } }, - { - success: true, - version, - status: { counter, timer: timer + 1, disabled, now: Date.now() }, - }, - ], - expectedLogs: [ - { - params: [ - { - parsedResponses: [ - { - signerUrl: url, - values: { - counter, - disabled, - timer, - version, - }, - }, - { - signerUrl: url, - values: { - counter, - disabled, - timer, - version, - }, - }, - { - signerUrl: url, - values: { - counter, - disabled, - timer: timer + 1, - version, - }, - }, - ], - }, - WarningMessage.SIGNER_RESPONSE_DISCREPANCIES, - ], - level: 'warn', - }, - ], - }, - ] - testCases.forEach((testCase) => { - it(testCase.it, () => { - const logSpys = { - info: { - spy: jest.spyOn(logger, 'info'), - callCount: 0, - }, - debug: { - spy: jest.spyOn(logger, 'debug'), - callCount: 0, - }, - warn: { - spy: jest.spyOn(logger, 'warn'), - callCount: 0, - }, - error: { - spy: jest.spyOn(logger, 'error'), - callCount: 0, - }, - } - logDomainResponseDiscrepancies( - logger, - testCase.responses.map((res) => ({ url, res })) - ) - testCase.expectedLogs.forEach((log) => { - expect(logSpys[log.level].spy).toHaveBeenNthCalledWith( - ++logSpys[log.level].callCount, - ...log.params - ) - }) - Object.values(logSpys).forEach((level) => { - level.spy.mockClear() - level.spy.mockRestore() - }) - }) - }) -}) diff --git a/packages/phone-number-privacy/combiner/test/unit/domain-threshold-state.test.ts b/packages/phone-number-privacy/combiner/test/unit/domain-threshold-state.test.ts deleted file mode 100644 index c66c4d478f..0000000000 --- a/packages/phone-number-privacy/combiner/test/unit/domain-threshold-state.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { - DomainQuotaStatusResponseSuccess, - DomainRestrictedSignatureResponseSuccess, - KeyVersionInfo, -} from '@celo/phone-number-privacy-common' -import { getSignerVersion } from '@celo/phone-number-privacy-signer/src/config' -import { findThresholdDomainState } from '../../src/domain/services/threshold-state' - -describe('domain threshold state', () => { - // TODO add tests with failed signer responses, depending on - // result of https://github.com/celo-org/celo-monorepo/issues/9826 - - const keyVersionInfo: KeyVersionInfo = { - keyVersion: 1, - threshold: 3, - polynomial: 'mock polynomial', - pubKey: 'mock pubKey', - } - - const totalSigners = 4 - - const expectedVersion = getSignerVersion() - const now = Date.now() - const timer = now - 1 - const counter = 2 - - const varyingDomainStates = [ - { - statuses: [ - { timer, counter: 2, disabled: false, now }, - { timer, counter: 2, disabled: false, now }, - { timer, counter: 2, disabled: false, now }, - { timer, counter: 2, disabled: false, now }, - ], - expectedCounter: 2, - expectedTimer: timer, - }, - { - statuses: [ - { timer, counter: 1, disabled: false, now }, - { timer, counter: 2, disabled: false, now }, - { timer, counter: 2, disabled: false, now }, - { timer, counter: 2, disabled: false, now }, - ], - expectedCounter: 2, - expectedTimer: timer, - }, - { - statuses: [ - { timer, counter: 0, disabled: false, now }, - { timer, counter: 1, disabled: false, now }, - { timer, counter: 2, disabled: false, now }, - { timer, counter: 3, disabled: false, now }, - ], - expectedCounter: 2, - expectedTimer: timer, - }, - { - statuses: [ - { timer, counter: 0, disabled: true, now }, - { timer, counter: 1, disabled: false, now }, - { timer, counter: 2, disabled: false, now }, - { timer, counter: 3, disabled: false, now }, - ], - expectedCounter: 3, - expectedTimer: timer, - }, - { - statuses: [ - { timer: timer - 1, counter, disabled: false, now }, - { timer, counter, disabled: false, now }, - { timer, counter, disabled: false, now }, - { timer, counter, disabled: false, now }, - ], - expectedCounter: counter, - expectedTimer: timer, - }, - { - statuses: [ - { timer: timer - 1, counter, disabled: false, now }, - { timer: timer - 1, counter, disabled: false, now }, - { timer: timer - 1, counter, disabled: false, now }, - { timer, counter, disabled: false, now }, - ], - expectedCounter: counter, - expectedTimer: timer - 1, - }, - { - statuses: [ - { timer: timer - 1, counter: 1, disabled: false, now }, - { timer, counter: 1, disabled: false, now }, - { timer, counter: 2, disabled: false, now }, - { timer, counter: 3, disabled: false, now }, - ], - expectedCounter: 2, - expectedTimer: timer, - }, - ] - - varyingDomainStates.forEach(({ statuses, expectedCounter, expectedTimer }) => { - it(`should return counter:${expectedCounter} and timer:${expectedTimer} given the domain states: ${statuses}`, () => { - const responses = statuses.map((status) => { - const res: DomainRestrictedSignatureResponseSuccess | DomainQuotaStatusResponseSuccess = { - success: true, - version: expectedVersion, - status, - } - return { url: 'random url', res, status: 200 } - }) - const thresholdResult = findThresholdDomainState(keyVersionInfo, responses, totalSigners) - - expect(thresholdResult).toStrictEqual({ - timer: expectedTimer, - counter: expectedCounter, - disabled: false, - now, - }) - }) - }) - - it('should return 0 values when too many disabled responses', () => { - const statuses = [ - { timer, counter: 0, disabled: true, now }, - { timer, counter: 1, disabled: true, now }, - { timer, counter: 2, disabled: false, now }, - { timer, counter: 2, disabled: false, now }, - ] - - const responses = statuses.map((status) => { - const res: DomainRestrictedSignatureResponseSuccess | DomainQuotaStatusResponseSuccess = { - success: true, - version: expectedVersion, - status, - } - return { url: 'random url', res, status: 200 } - }) - const thresholdResult = findThresholdDomainState(keyVersionInfo, responses, totalSigners) - - expect(thresholdResult).toStrictEqual({ timer: 0, counter: 0, disabled: true, now: 0 }) - }) - - it('should throw an error if not enough signer responses', () => { - const statuses = [ - { timer, counter: 1, disabled: true, now }, - { timer, counter: 2, disabled: false, now }, - { timer, counter: 2, disabled: false, now }, - ] - const responses = statuses.map((status) => { - const res: DomainRestrictedSignatureResponseSuccess | DomainQuotaStatusResponseSuccess = { - success: true, - version: expectedVersion, - status, - } - return { url: 'random url', res, status: 200 } - }) - - expect(() => findThresholdDomainState(keyVersionInfo, responses, totalSigners)).toThrow( - 'Insufficient number of signer responses. Domain may be disabled' - ) - }) -}) diff --git a/packages/phone-number-privacy/combiner/test/unit/pnp-response-logger.test.ts b/packages/phone-number-privacy/combiner/test/unit/pnp-response-logger.test.ts deleted file mode 100644 index 85512f04af..0000000000 --- a/packages/phone-number-privacy/combiner/test/unit/pnp-response-logger.test.ts +++ /dev/null @@ -1,449 +0,0 @@ -import { - KeyVersionInfo, - OdisResponse, - PnpQuotaRequest, - rootLogger, - SignMessageRequest, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { getSignerVersion } from '@celo/phone-number-privacy-signer/src/config' -import config, { - MAX_QUERY_COUNT_DISCREPANCY_THRESHOLD, - MAX_TOTAL_QUOTA_DISCREPANCY_THRESHOLD, -} from '../../src/config' -import { logPnpSignerResponseDiscrepancies } from '../../src/pnp/services/log-responses' - -describe('pnp response logger', () => { - const url = 'test signer url' - - const keyVersionInfo: KeyVersionInfo = { - keyVersion: 1, - threshold: 3, - polynomial: 'mock polynomial', - pubKey: 'mock pubKey', - } - - const pnpConfig = config.phoneNumberPrivacy - pnpConfig.keys.currentVersion = keyVersionInfo.keyVersion - pnpConfig.keys.versions = JSON.stringify([keyVersionInfo]) - - const version = getSignerVersion() - - const totalQuota = 10 - const performedQueryCount = 5 - const warnings = ['warning'] - - const testCases: { - it: string - responses: OdisResponse[] - expectedLogs: { - params: string | any[] - level: 'info' | 'debug' | 'warn' | 'error' - }[] - }[] = [ - { - it: 'should log correctly when no responses provided', - responses: [], - expectedLogs: [ - { - params: ['No successful signer responses found!'], - level: 'warn', - }, - ], - }, - { - it: 'should log correctly when all the responses are the same', - responses: [ - { success: true, performedQueryCount, totalQuota, version, warnings }, - { success: true, performedQueryCount, totalQuota, version, warnings }, - { success: true, performedQueryCount, totalQuota, version, warnings }, - ], - expectedLogs: [], - }, - { - it: 'should log correctly when there is a discrepency in version field', - responses: [ - { - success: true, - performedQueryCount, - totalQuota, - version: 'differentVersion', - - warnings, - }, - { success: true, performedQueryCount, totalQuota, version, warnings }, - { success: true, performedQueryCount, totalQuota, version, warnings }, - ], - expectedLogs: [ - { - params: [ - { - parsedResponses: [ - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version: 'differentVersion', - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - ], - }, - WarningMessage.SIGNER_RESPONSE_DISCREPANCIES, - ], - level: 'warn', - }, - ], - }, - { - it: 'should log correctly when there is a discrepency in performedQueryCount field', - responses: [ - { success: true, performedQueryCount: 1, totalQuota, version, warnings }, - { success: true, performedQueryCount, totalQuota, version, warnings }, - { success: true, performedQueryCount, totalQuota, version, warnings }, - ], - expectedLogs: [ - { - params: [ - { - parsedResponses: [ - { - signerUrl: url, - values: { - performedQueryCount: 1, - totalQuota, - version, - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - ], - }, - WarningMessage.SIGNER_RESPONSE_DISCREPANCIES, - ], - level: 'warn', - }, - ], - }, - { - it: 'should log correctly when there is a large discrepency in performedQueryCount field', - responses: [ - { - success: true, - performedQueryCount: performedQueryCount + MAX_QUERY_COUNT_DISCREPANCY_THRESHOLD, - totalQuota, - version, - - warnings, - }, - { success: true, performedQueryCount, totalQuota, version, warnings }, - { success: true, performedQueryCount, totalQuota, version, warnings }, - ], - expectedLogs: [ - { - params: [ - { - parsedResponses: [ - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount: - performedQueryCount + MAX_QUERY_COUNT_DISCREPANCY_THRESHOLD, - totalQuota, - version, - warnings, - }, - }, - ], - }, - WarningMessage.SIGNER_RESPONSE_DISCREPANCIES, - ], - level: 'warn', - }, - { - params: [ - { - sortedByQueryCount: [ - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount: - performedQueryCount + MAX_QUERY_COUNT_DISCREPANCY_THRESHOLD, - totalQuota, - version, - warnings, - }, - }, - ], - }, - WarningMessage.INCONSISTENT_SIGNER_QUERY_MEASUREMENTS, - ], - level: 'error', - }, - ], - }, - { - it: 'should log correctly when there is a discrepency in totalQuota field', - responses: [ - { success: true, performedQueryCount, totalQuota, version, warnings }, - { success: true, performedQueryCount, totalQuota, version, warnings }, - { success: true, performedQueryCount, totalQuota: 1, version, warnings }, - ], - expectedLogs: [ - { - params: [ - { - parsedResponses: [ - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota: 1, - version, - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - ], - }, - WarningMessage.SIGNER_RESPONSE_DISCREPANCIES, - ], - level: 'warn', - }, - ], - }, - { - it: 'should log correctly when there is a large discrepency in totalQuota field', - responses: [ - { success: true, performedQueryCount, totalQuota, version, warnings }, - { success: true, performedQueryCount, totalQuota, version, warnings }, - { - success: true, - performedQueryCount, - totalQuota: totalQuota + MAX_TOTAL_QUOTA_DISCREPANCY_THRESHOLD, - version, - - warnings, - }, - ], - expectedLogs: [ - { - params: [ - { - sortedByTotalQuota: [ - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota: totalQuota + MAX_TOTAL_QUOTA_DISCREPANCY_THRESHOLD, - version, - warnings, - }, - }, - ], - }, - WarningMessage.INCONSISTENT_SIGNER_QUOTA_MEASUREMENTS, - ], - level: 'error', - }, - ], - }, - { - it: 'should log correctly when there is a discrepency in warnings field', - responses: [ - { success: true, performedQueryCount, totalQuota, version, warnings }, - { success: true, performedQueryCount, totalQuota, version, warnings }, - { - success: true, - performedQueryCount, - totalQuota, - version, - - warnings: ['differentWarning'], - }, - ], - expectedLogs: [ - { - params: [ - { - parsedResponses: [ - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings, - }, - }, - { - signerUrl: url, - values: { - performedQueryCount, - totalQuota, - version, - warnings: ['differentWarning'], - }, - }, - ], - }, - WarningMessage.SIGNER_RESPONSE_DISCREPANCIES, - ], - level: 'warn', - }, - ], - }, - ] - testCases.forEach((testCase) => { - it(testCase.it, () => { - const logger = rootLogger(config.serviceName) - - const responses = testCase.responses.map((res) => ({ res, url })) - const logSpys = { - info: { - spy: jest.spyOn(logger, 'info'), - callCount: 0, - }, - debug: { - spy: jest.spyOn(logger, 'debug'), - callCount: 0, - }, - warn: { - spy: jest.spyOn(logger, 'warn'), - callCount: 0, - }, - error: { - spy: jest.spyOn(logger, 'error'), - callCount: 0, - }, - } - logPnpSignerResponseDiscrepancies(logger, responses) - testCase.expectedLogs.forEach((log) => { - expect(logSpys[log.level].spy).toHaveBeenNthCalledWith( - ++logSpys[log.level].callCount, - ...log.params - ) - }) - Object.values(logSpys).forEach((level) => { - level.spy.mockClear() - level.spy.mockRestore() - }) - }) - }) -}) diff --git a/packages/phone-number-privacy/combiner/test/unit/pnp-threshold-state.test.ts b/packages/phone-number-privacy/combiner/test/unit/pnp-threshold-state.test.ts deleted file mode 100644 index 47b21e041c..0000000000 --- a/packages/phone-number-privacy/combiner/test/unit/pnp-threshold-state.test.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { KeyVersionInfo, WarningMessage } from '@celo/phone-number-privacy-common' -import { getSignerVersion } from '@celo/phone-number-privacy-signer/src/config' - -import config from '../../src/config' -import { findCombinerQuotaState } from '../../src/pnp/services/threshold-state' - -describe('pnp threshold state', () => { - // TODO add tests with failed signer responses, depending on - // result of https://github.com/celo-org/celo-monorepo/issues/9826 - - const keyVersionInfo: KeyVersionInfo = { - keyVersion: 1, - threshold: 3, - polynomial: 'mock polynomial', - pubKey: 'mock pubKey', - } - - const pnpConfig = config.phoneNumberPrivacy - pnpConfig.keys.currentVersion = keyVersionInfo.keyVersion - pnpConfig.keys.versions = JSON.stringify([keyVersionInfo]) - - const expectedVersion = getSignerVersion() - const totalQuota = 10 - const performedQueryCount = 5 - - const varyingQueryCount = [ - { - signerRes: [ - { performedQueryCount: 0, totalQuota }, - { performedQueryCount: 0, totalQuota }, - { performedQueryCount: 0, totalQuota }, - { performedQueryCount: 0, totalQuota }, - ], - expectedQueryCount: 0, - }, - { - signerRes: [ - { performedQueryCount: 0, totalQuota }, - { performedQueryCount: 0, totalQuota }, - { performedQueryCount: 0, totalQuota }, - { performedQueryCount: 1, totalQuota }, - ], - expectedQueryCount: 0, - }, // does not reach threshold - { - signerRes: [ - { performedQueryCount: 1, totalQuota }, - { performedQueryCount: 1, totalQuota }, - { performedQueryCount: 0, totalQuota }, - { performedQueryCount: 1, totalQuota }, - ], - expectedQueryCount: 1, - }, // threshold reached - { - signerRes: [ - { performedQueryCount: 0, totalQuota }, - { performedQueryCount: 1, totalQuota }, - { performedQueryCount: 1, totalQuota }, - { performedQueryCount: 1, totalQuota }, - ], - expectedQueryCount: 1, - }, // order of signers shouldn't matter - { - signerRes: [ - { performedQueryCount: 1, totalQuota }, - { performedQueryCount: 4, totalQuota }, - { performedQueryCount: 9, totalQuota }, - { performedQueryCount: 11, totalQuota }, - ], - expectedQueryCount: 9, - }, - ] - varyingQueryCount.forEach(({ signerRes, expectedQueryCount }) => { - it(`should return ${expectedQueryCount} performedQueryCount given signer responses of ${signerRes}`, () => { - const responses = signerRes.map((o) => { - return { - url: 'random url', - status: 200, - res: { - success: true as true, - version: expectedVersion, - ...o, - }, - } - }) - - const warnings: string[] = [] - const thresholdResult = findCombinerQuotaState(keyVersionInfo, responses, warnings) - expect(thresholdResult).toStrictEqual({ - performedQueryCount: expectedQueryCount, - totalQuota, - }) - }) - }) - - const varyingTotalQuota = [ - { - signerRes: [ - { performedQueryCount, totalQuota }, - { performedQueryCount, totalQuota }, - { performedQueryCount, totalQuota }, - { performedQueryCount, totalQuota }, - ], - expectedTotalQuota: totalQuota, - warning: false, - }, - { - signerRes: [ - { performedQueryCount, totalQuota: 7 }, - { performedQueryCount, totalQuota: 8 }, - { performedQueryCount, totalQuota: 9 }, - { performedQueryCount, totalQuota: 10 }, - ], - expectedTotalQuota: 8, - warning: true, - }, - { - signerRes: [ - { performedQueryCount, totalQuota: 8 }, - { performedQueryCount, totalQuota: 9 }, - { performedQueryCount, totalQuota: 10 }, - { performedQueryCount, totalQuota: 7 }, - ], - expectedTotalQuota: 8, - warning: true, - }, - ] - varyingTotalQuota.forEach(({ signerRes, expectedTotalQuota, warning }) => { - it(`should return ${expectedTotalQuota} totalQuota given signer responses of ${signerRes}`, () => { - const responses = signerRes.map((o) => { - return { - url: 'random url', - status: 200, - res: { - success: true as true, - version: expectedVersion, - ...o, - }, - } - }) - - const warnings: string[] = [] - const thresholdResult = findCombinerQuotaState(keyVersionInfo, responses, warnings) - expect(thresholdResult).toStrictEqual({ - performedQueryCount, - totalQuota: expectedTotalQuota, - }) - if (warning) { - expect(warnings).toContain( - WarningMessage.INCONSISTENT_SIGNER_QUOTA_MEASUREMENTS + - ', using threshold signer as best guess' - ) - } - }) - }) - - const varyingQuotaAndQuery = [ - { - signerRes: [ - { performedQueryCount: 1, totalQuota: 10 }, - { performedQueryCount: 2, totalQuota: 9 }, - { performedQueryCount: 3, totalQuota: 8 }, - { performedQueryCount: 4, totalQuota: 7 }, - ], - expectedQueryCount: 3, - expectedTotalQuota: 8, - warning: true, - }, - { - signerRes: [ - { performedQueryCount: 1, totalQuota: 7 }, - { performedQueryCount: 2, totalQuota: 8 }, - { performedQueryCount: 5, totalQuota: 9 }, - { performedQueryCount: 6, totalQuota: 10 }, - ], - expectedQueryCount: 5, - expectedTotalQuota: 9, - warning: true, - }, - ] - varyingQuotaAndQuery.forEach(({ signerRes, expectedQueryCount, expectedTotalQuota, warning }) => { - it(`should return ${expectedTotalQuota} totalQuota and ${expectedQueryCount} performedQueryCount given signer responses of ${signerRes}`, () => { - const responses = signerRes.map((o) => { - return { - url: 'random url', - status: 200, - res: { - success: true as true, - version: expectedVersion, - ...o, - }, - } - }) - - const warnings: string[] = [] - const thresholdResult = findCombinerQuotaState(keyVersionInfo, responses, warnings) - expect(thresholdResult).toStrictEqual({ - performedQueryCount: expectedQueryCount, - totalQuota: expectedTotalQuota, - }) - if (warning) { - expect(warnings).toContain( - WarningMessage.INCONSISTENT_SIGNER_QUOTA_MEASUREMENTS + - ', using threshold signer as best guess' - ) - } - }) - }) - - it('should throw an error if the total quota varies too much between signers', () => { - const signerRes = [ - { performedQueryCount, totalQuota: 1 }, - { performedQueryCount, totalQuota: 9 }, - { performedQueryCount, totalQuota: 15 }, - { performedQueryCount, totalQuota: 14 }, - ] - const responses = signerRes.map((o) => { - return { - url: 'random url', - status: 200, - res: { - success: true as true, - version: expectedVersion, - ...o, - }, - } - }) - - const warnings: string[] = [] - expect(() => findCombinerQuotaState(keyVersionInfo, responses, warnings)).toThrow( - WarningMessage.INCONSISTENT_SIGNER_QUOTA_MEASUREMENTS - ) - }) -}) diff --git a/packages/phone-number-privacy/combiner/test/utils.ts b/packages/phone-number-privacy/combiner/test/utils.ts deleted file mode 100644 index bbd2e6f21d..0000000000 --- a/packages/phone-number-privacy/combiner/test/utils.ts +++ /dev/null @@ -1,14 +0,0 @@ -import threshold_bls from 'blind-threshold-bls' -import { Server } from 'http' -import { Server as HttpsServer } from 'https' - -export function getBlindedPhoneNumber(phoneNumber: string, blindingFactor: Buffer): string { - const blindedPhoneNumber = threshold_bls.blind(Buffer.from(phoneNumber), blindingFactor).message - return Buffer.from(blindedPhoneNumber).toString('base64') -} - -export async function serverClose(server?: Server | HttpsServer) { - if (server) { - await new Promise((resolve) => server.close(resolve)) - } -} diff --git a/packages/phone-number-privacy/combiner/tsconfig.json b/packages/phone-number-privacy/combiner/tsconfig.json deleted file mode 100644 index c9ee1249ba..0000000000 --- a/packages/phone-number-privacy/combiner/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "plugins": [ - { - "name": "typescript-tslint-plugin" - } - ], - "lib": ["es2017"], - "module": "commonjs", - "strict": true, - "allowJs": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": true, - "target": "es2017", - "rootDir": "src", - "outDir": "./dist", - "skipLibCheck": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "preserveConstEnums": true, - "composite": true - }, - "include": ["src"], - "compileOnSave": true -} diff --git a/packages/phone-number-privacy/combiner/tslint.json b/packages/phone-number-privacy/combiner/tslint.json deleted file mode 100644 index 35e8972b59..0000000000 --- a/packages/phone-number-privacy/combiner/tslint.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": ["@celo/typescript/tslint.json"], - "rules": { - "no-implicit-dependencies": [ - true, - [ - "node:assert" - ] - ], - "no-global-arrow-functions": false, - "no-console": true - } -} diff --git a/packages/phone-number-privacy/common/.gitignore b/packages/phone-number-privacy/common/.gitignore deleted file mode 100644 index 3709fe818d..0000000000 --- a/packages/phone-number-privacy/common/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -lib/ - -package-lock.json \ No newline at end of file diff --git a/packages/phone-number-privacy/common/README.md b/packages/phone-number-privacy/common/README.md deleted file mode 100644 index 48349f4627..0000000000 --- a/packages/phone-number-privacy/common/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# ODIS Common Package - -This package contains common code used across ODIS. It is depended on by the Combiner, Signer and Monitor services as well as the @celo/identity and @celo/encrypted-backup SDKS. In most cases where code will be re-used by multiple parts of ODIS, it probably belongs here. - -## Notable Contents - -- The request and response type schemas for all ODIS APIs. -- Error and Warning types used for monitoring and alerting in both the Combiner and Signer. -- The PEAR Sequential Delay rate limiting algorithm. - -## Release Process - -When updating the ODIS common package, it is important to remember that all changes must be published before they can be used in production ODIS services or SDKS. If your changes are needed in the SDKS, then you will need to also publish all the Celo SDKs. The instructions below detail this entire SDK release process, but if your changes are only needed in ODIS services you only need to do step 7 (remember to run `yarn && yarn build` before publishing, and consider reading the rest of the steps anyway for context) - -These instructions assume the following scenario for readability: - -- The latest released sdk version is `3.1.0` -- The SDK versions in the monorepo are all set to `3.1.1-dev` -- You are releasing version `3.2.0` of the SDKs -- The latest released ODIS common package version is `2.0.2` -- You are releasing version `2.0.3` of the ODIS common package - -1. Checkout a new branch for the SDK release. Name it something like `/release3.2.0` -2. Note that you should release version `3.2.0-beta.1` and `2.0.3-beta.1` and test that everything is working correctly before publishing them as `latest`. If everything is not working correctly, try again with `-beta.2` -3. Search and replace all instances of the current sdk version in the monorepo with the new sdk version you are releasing (check the search and replace changes do what you intend them to before hitting replace!) - - i.e. search and replace `3.1.1-dev` with `3.2.0-beta.1` (note that we’ve removed the `-dev`) -4. Same idea as above -- ensure the version of the `@celo/phone-number-privacy-common` package is set to the version you are trying to release (i.e. `2.0.3-beta.1`) and that all other packages are importing this version. -5. From the monorepo root directory, run `yarn reset && yarn && yarn build` (expect this to take at least 10 mins) -6. Commit your changes with the message `3.2.0-beta.1` -7. Publish the ODIS common package by navigating to the `phone-number-privacy/common` directory and running `npm publish —-tag beta` - - You will be prompted to enter your OTP - - When publishing as `latest`, omit the `--tag beta` -8. Publish the sdks by running `npm run deploy-sdks` from the monorepo root directory - - You will be prompted to enter a version number that you wish to publish. i.e. `3.2.0-beta.1` - - You will be repeatedly asked to enter your OTP, which will be automatically supplied if you hit ‘enter’ (you do not have to paste it to the command line each time) - - When your OTP expires, you will see an error and will have to re-enter the new one - - Note the `deploy-sdks` script will automatically append `-dev` to all the sdk versions after they're published. You may need to search and replace to undue this if you were publishing a beta release. -9. Depending on what you're releasing, you may want to test that the newly published SDKs work as intended. This may be as simple as checking that CI runs successfully on your `3.2.0-beta.1` commit. -10. Once you are confident in the beta release, repeat steps 3 through 9 with versions `3.2.0` and `2.0.3`. The SDKs will be published with the `latest` tag. -11. The `deploy-sdks` script will automatically append `-dev` to all the sdk versions after they're published. For `latest` releases, it will also increment to the next patch version. Please ensure this happened correctly and commit the result with the message `3.2.1-dev` -12. Get your PR for the release branch reviewed and merged - - - If CI fails with output like below, it means that some packages outside of the SDK did not get incremented to `3.2.1-dev`. Please go through and make sure these are all incremented correctly and CI should pass. - - ``` - ./sdk/utils/src/address.ts(1,46): error TS2307: Cannot find module '@celo/base/lib/address' or its corresponding type declarations. - ../sdk/utils/src/address.ts(27,8): error TS2307: Cannot find module '@celo/base/lib/address' or its corresponding type declarations. - ../sdk/utils/src/async.ts(10,8): error TS2307: Cannot find module '@celo/base/lib/async' or its corresponding type declarations - ``` - -13. Don’t forget to tag the PR commit as a release in GitHub and add Release Notes diff --git a/packages/phone-number-privacy/common/index.d.ts b/packages/phone-number-privacy/common/index.d.ts deleted file mode 100644 index 102dc17cf1..0000000000 --- a/packages/phone-number-privacy/common/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'bunyan-debug-stream' diff --git a/packages/phone-number-privacy/common/jest.config.js b/packages/phone-number-privacy/common/jest.config.js deleted file mode 100644 index ac1faeacbb..0000000000 --- a/packages/phone-number-privacy/common/jest.config.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - preset: 'ts-jest', - coverageReporters: [['lcov', { projectRoot: '../../../' }], 'text'], - collectCoverageFrom: ['./src/**'], - coverageThreshold: { - global: { - lines: 80, - }, - }, -} diff --git a/packages/phone-number-privacy/common/package.json b/packages/phone-number-privacy/common/package.json deleted file mode 100644 index 03fda8fa0a..0000000000 --- a/packages/phone-number-privacy/common/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "@celo/phone-number-privacy-common", - "version": "3.0.3", - "description": "Common library for the combiner and signer libraries", - "author": "Celo", - "license": "Apache-2.0", - "main": "./lib/index.js", - "types": "./lib/index.d.ts", - "scripts": { - "prepublishOnly": "yarn build", - "build": "tsc -b .", - "clean": "tsc -b . --clean", - "test": "jest --testPathIgnorePatterns test/end-to-end", - "test:coverage": "yarn test --coverage", - "lint": "tslint -c tslint.json --project ." - }, - "files": [ - "lib/**/*" - ], - "dependencies": { - "@celo/base": "^5.0.4", - "@celo/contractkit": "^5.0.4", - "@celo/utils": "^5.0.4", - "@celo/phone-utils": "^5.0.4", - "@types/bunyan": "1.8.8", - "bignumber.js": "^9.0.0", - "bunyan": "1.8.12", - "bunyan-debug-stream": "2.0.0", - "bunyan-gke-stackdriver": "0.1.2", - "dotenv": "^8.2.0", - "elliptic": "^6.5.4", - "io-ts": "2.0.1", - "fp-ts": "2.1.1", - "express": "^4.17.6", - "node-fetch": "^2.6.9", - "is-base64": "^1.1.0", - "@opentelemetry/api": "^1.4.1", - "@opentelemetry/auto-instrumentations-node": "^0.38.0", - "@opentelemetry/propagator-ot-trace": "^0.27.0", - "@opentelemetry/sdk-metrics": "^1.15.1", - "@opentelemetry/sdk-node": "^0.41.1", - "@opentelemetry/semantic-conventions": "^1.15.1", - "@opentelemetry/sdk-trace-web": "^1.15.1" - }, - "devDependencies": { - "@celo/poprf": "^0.1.9", - "@celo/wallet-local": "^5.0.4", - "@types/elliptic": "^6.4.12", - "@types/express": "^4.17.6", - "@types/is-base64": "^1.1.0", - "@types/node-fetch": "^2.5.7" - }, - "engines": { - "node": ">=12" - } -} \ No newline at end of file diff --git a/packages/phone-number-privacy/common/src/domains/constants.ts b/packages/phone-number-privacy/common/src/domains/constants.ts deleted file mode 100644 index 78179ce6f0..0000000000 --- a/packages/phone-number-privacy/common/src/domains/constants.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Enum of identifiers (i.e. names) for all standardized domains - * - * @privateRemarks DomainIdentifiers is defined in a separate file to avoid issues with circular - * dependencies causing undefined errors. - */ -export enum DomainIdentifiers { - SequentialDelay = 'ODIS Sequential Delay Domain', -} diff --git a/packages/phone-number-privacy/common/src/domains/domains.ts b/packages/phone-number-privacy/common/src/domains/domains.ts deleted file mode 100644 index 95fd41d3a2..0000000000 --- a/packages/phone-number-privacy/common/src/domains/domains.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { - EIP712TypedData, - EIP712TypesWithPrimary, - generateTypedDataHash, -} from '@celo/utils/lib/sign-typed-data-utils' -import * as t from 'io-ts' -import { - isSequentialDelayDomain, - SequentialDelayDomain, - sequentialDelayDomainEIP712Types, - SequentialDelayDomainOptions, - sequentialDelayDomainOptionsEIP712Types, - SequentialDelayDomainSchema, - SequentialDelayDomainState, -} from './sequential-delay' - -/** - * Union type of domains which are currently implmented and standardized for use with ODIS. - * Domains should be added to the union type as they are implemented. - * - * @remarks Additional domain types should be added to this type union as they are standardized. - * - * All new Domain types must contain the fields { name: string, version: string }. Domain types - * may have additional fields, which must be assignable to EIP712Value. See CIP-40 for more details: - * - * https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0040.md#extension-for-domain-restricted-requests - */ -export type Domain = SequentialDelayDomain - -/** - * Parameterized union type of standardized domain options. - * - * @remarks If the type parameter is specified to be a concrete Domain subtype, then only its - * associated DomainOptions is selected and assignable to the parameterized type. - * - * Additional domain options types should be added to this type union along with the new domain type - * definition, if the new domain type has associated options. If a domain type has no associated - * options, it's corresponding options type should be an empty struct. - * - * Domain options must be assignable to EIP712Object. - */ -export type DomainOptions = D extends SequentialDelayDomain - ? SequentialDelayDomainOptions - : never - -/** - * Parameterized union type of currently implemented and standarized domain state structs. If the - * type parameter is specified to be a concrete Domain subtype, then only its associated - * Domain state type is selected and assignable to the parameterized type. - */ -export type DomainState = D extends SequentialDelayDomain - ? SequentialDelayDomainState - : never - -/** io-ts schema for encoding and decoding domains of any standardized type */ -export const DomainSchema: t.Type = SequentialDelayDomainSchema - -export function domainEIP712Types(domain: Domain): EIP712TypesWithPrimary { - if (isSequentialDelayDomain(domain)) { - return sequentialDelayDomainEIP712Types - } - - // canary provides a compile-time check that all subtypes of Domain have branches. If a case - // was missed, then an error will report that domain cannot be assigned to type `never`. - const canary = (x: never) => x - canary(domain) - throw new Error('Implementation error. Input of type Domain was not recognized') -} - -export function domainOptionsEIP712Types(domain: Domain): EIP712TypesWithPrimary { - if (isSequentialDelayDomain(domain)) { - return sequentialDelayDomainOptionsEIP712Types - } - - // canary provides a compile-time check that all subtypes of Domain have branches. If a case - // was missed, then an error will report that domain cannot be assigned to type `never`. - const canary = (x: never) => x - canary(domain) - throw new Error('Implementation error. Input of type Domain was not recognized') -} - -/** - * Wraps a domain instance of a standardized type into an EIP-712 typed data structure, including - * the EIP-712 type signature specififed by the mapping from TypeScript types in CIP-40. - * https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0040.md#mapping-typescript-to-eip-712-types - */ -export const domainEIP712 = (domain: Domain): EIP712TypedData => ({ - types: { - ...domainEIP712Types(domain).types, - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - ], - }, - primaryType: domainEIP712Types(domain).primaryType, - domain: { - name: domain.name, - version: domain.version, - }, - message: domain, -}) - -/** - * Produces the canonical 256-bit EIP-712 typed hash of the given domain. - * - * @remarks Note that this is a simple wrapper to get the EIP-712 hash after encoding it to an - * EIP-712 typed data format. If a signature over the domain is needed, encode to EIP-712 format - * and pass that into a signTypedData function. - */ -export function domainHash(domain: Domain): Buffer { - return generateTypedDataHash(domainEIP712(domain)) -} diff --git a/packages/phone-number-privacy/common/src/domains/index.ts b/packages/phone-number-privacy/common/src/domains/index.ts deleted file mode 100644 index 4befe2657c..0000000000 --- a/packages/phone-number-privacy/common/src/domains/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './constants' -export * from './domains' -export * from './sequential-delay' diff --git a/packages/phone-number-privacy/common/src/domains/sequential-delay.ts b/packages/phone-number-privacy/common/src/domains/sequential-delay.ts deleted file mode 100644 index 91de2c0064..0000000000 --- a/packages/phone-number-privacy/common/src/domains/sequential-delay.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { - EIP712Optional, - eip712OptionalSchema, - eip712OptionalType, - EIP712TypesWithPrimary, -} from '@celo/utils/lib/sign-typed-data-utils' -import * as t from 'io-ts' -import { DomainIdentifiers } from './constants' -import { Domain } from './domains' - -// Concrete Domain subtypes are only assignable to Domain and EIP712Object when using type instead -// of interface. Otherwise the compiler complains about a missing index signature. -// tslint:disable:interface-over-type-literal - -export type SequentialDelayStage = { - /** - * How many seconds each batch of attempts in this stage is delayed with - * respect to the timer. - */ - delay: number - /** - * Whether the timer should be reset between attempts during this stage. - * Defaults to true. - */ - resetTimer: EIP712Optional - /** - * The number of continuous attempts a user gets before the next delay - * in each repetition of this stage. Defaults to 1. - */ - batchSize: EIP712Optional - /** - * The number of times this stage repeats before continuing to the next stage - * in the RateLimit array. Defaults to 1. - */ - repetitions: EIP712Optional -} - -export type SequentialDelayDomain = { - name: DomainIdentifiers.SequentialDelay - version: '1' - stages: SequentialDelayStage[] - /** - * Optional Celo address against which signed requests must be authenticated. - * In the case of Cloud Backup, this will be derived from a one-time key stored with the ciphertext. - * Encoded as a checksummed address with leading "0x". - */ - address: EIP712Optional - /** - * Optional string to distinguish the output of this domain instance from - * other SequentialDelayDomain instances - */ - salt: EIP712Optional -} - -export type SequentialDelayDomainOptions = { - /** - * EIP-712 signature over the entire request by the address specified in the domain. - * Required if `address` is defined in the domain instance. If `address` is - * not defined in the domain instance, then a signature must not be provided. - * Encoded as a hex string with leading 0x. - */ - signature: EIP712Optional - /** - * Used to prevent replay attacks. Required if a signature is provided. - * Code verifying the signature for rate limiting should check this nonce against a counter of - * applied requests. E.g. Ensure the nonce is 0 on the first request and 2 on the third. - */ - nonce: EIP712Optional -} - -export interface SequentialDelayDomainState { - /** - * Timestamp in seconds since the Unix Epoch to which the next delay should be applied - * to calculate when a new request will be accepted. - */ - timer: number - /** Number of queries that have been accepted for the SequentialDelayDomain instance. */ - counter: number - /** Whether or not the domain has been disabled. If disabled, no more queries will be served. */ - disabled: boolean - /** Server timestamp in seconds since the Unix Epoch. */ - now: number -} - -export const INITIAL_SEQUENTIAL_DELAY_DOMAIN_STATE: SequentialDelayDomainState = { - timer: 0, - counter: 0, - disabled: false, - now: 0, -} - -/** io-ts schema for encoding and decoding SequentialDelayStage structs */ -export const SequentialDelayStageSchema: t.Type = t.strict({ - delay: t.number, - resetTimer: eip712OptionalSchema(t.boolean), - batchSize: eip712OptionalSchema(t.number), - repetitions: eip712OptionalSchema(t.number), -}) - -/** io-ts schema for encoding and decoding SequentialDelayDomain structs */ -export const SequentialDelayDomainSchema: t.Type = t.strict({ - name: t.literal(DomainIdentifiers.SequentialDelay), - version: t.literal('1'), - stages: t.array(SequentialDelayStageSchema), - address: eip712OptionalSchema(t.string), - salt: eip712OptionalSchema(t.string), -}) - -/** io-ts schema for encoding and decoding SequentialDelayDomainOptions structs */ -export const SequentialDelayDomainOptionsSchema: t.Type = t.strict({ - signature: eip712OptionalSchema(t.string), - nonce: eip712OptionalSchema(t.number), -}) - -/** io-ts schema for encoding and decoding SequentialDelayDomainState structs */ -export const SequentialDelayDomainStateSchema: t.Type = t.strict({ - timer: t.number, - counter: t.number, - disabled: t.boolean, - now: t.number, -}) - -export const isSequentialDelayDomain = (domain: Domain): domain is SequentialDelayDomain => - domain.name === DomainIdentifiers.SequentialDelay && domain.version === '1' - -export const sequentialDelayDomainEIP712Types: EIP712TypesWithPrimary = { - types: { - SequentialDelayDomain: [ - { name: 'address', type: 'Optional
' }, - { name: 'name', type: 'string' }, - { name: 'salt', type: 'Optional' }, - { name: 'stages', type: 'SequentialDelayStage[]' }, - { name: 'version', type: 'string' }, - ], - SequentialDelayStage: [ - { name: 'batchSize', type: 'Optional' }, - { name: 'delay', type: 'uint256' }, - { name: 'repetitions', type: 'Optional' }, - { name: 'resetTimer', type: 'Optional' }, - ], - ...eip712OptionalType('address'), - ...eip712OptionalType('string'), - ...eip712OptionalType('uint256'), - ...eip712OptionalType('bool'), - }, - primaryType: 'SequentialDelayDomain', -} - -export const sequentialDelayDomainOptionsEIP712Types: EIP712TypesWithPrimary = { - types: { - SequentialDelayDomainOptions: [ - { name: 'nonce', type: 'Optional' }, - { name: 'signature', type: 'Optional' }, - ], - ...eip712OptionalType('string'), - ...eip712OptionalType('uint256'), - }, - primaryType: 'SequentialDelayDomainOptions', -} - -/** Result values of the sequential delay domain rate limiting function */ -export interface SequentialDelayResultAccepted { - /** Whether or not a request will be accepted at the given time */ - accepted: true - /** State after applying an additional query against the quota */ - state: SequentialDelayDomainState -} - -export interface SequentialDelayResultRejected { - /** Whether or not a request will be accepted at the given time */ - accepted: false - /** State after rejecting the request. Should be unchanged. */ - state: SequentialDelayDomainState - /** - * Earliest time a request will be accepted at the current stage. - * Undefined if a request will never be accepted. - */ - notBefore?: number -} - -export type SequentialDelayResult = SequentialDelayResultAccepted | SequentialDelayResultRejected - -interface IndexedSequentialDelayStage extends SequentialDelayStage { - // The attempt number at which the stage begins - start: number -} - -/** - * Rate limiting predicate for the sequential delay domain - * - * @param domain SequentialDelayDomain instance against which the rate limit is being calculated, - * and which supplied the rate limiting parameters. - * @param attemptTime The Unix timestamp in seconds when the request was received. - * @param state The current state of the domain, including the used quota counter and timer values. - * Defaults to initial state if no state is available (i.e. for first request against the domain). - */ -export const checkSequentialDelayRateLimit = ( - domain: SequentialDelayDomain, - attemptTime: number, - state: SequentialDelayDomainState = INITIAL_SEQUENTIAL_DELAY_DOMAIN_STATE -): SequentialDelayResult => { - // If the domain has been disabled, all queries are to be rejected. - if (state.disabled) { - return { accepted: false, state: { ...state, now: attemptTime } } - } - - const stage = getIndexedStage(domain, state.counter) - - // If the counter is past the last stage (i.e. the domain is permanently out of quota) return early. - if (!stage) { - return { accepted: false, state: { ...state, now: attemptTime } } - } - - const resetTimer = stage.resetTimer.defined ? stage.resetTimer.value : true - const delay = getDelay(stage, state.counter) - const notBefore = state.timer + delay - - if (attemptTime < notBefore) { - return { accepted: false, notBefore, state: { ...state, now: attemptTime } } - } - - // Request is accepted. Update the state. - return { - accepted: true, - state: { - counter: state.counter + 1, - timer: resetTimer ? attemptTime : notBefore, - disabled: state.disabled, - now: attemptTime, - }, - } -} - -/** - * Finds the current stage of the SequentialDelayDomain rate limit for a given attempt number - * - * @param domain SequentialDelayDomain instance against which the rate limit is being calculated, - * and which supplied the rate limiting parameters. - * @param counter The current attempt number - */ -const getIndexedStage = ( - domain: SequentialDelayDomain, - counter: number -): IndexedSequentialDelayStage | undefined => { - // The attempt index marking the beginning of the current stage - let start = 0 - // The index of the current stage in domain.stages[] - let index = 0 - // The number of attempts in the current stage - let attemptsInStage = 0 - while (start <= counter) { - if (index >= domain.stages.length) { - // Counter is past the last stage (i.e. the domain is permanently out of quota) - return undefined - } - const stage = domain.stages[index] - const repetitions = stage.repetitions.defined ? stage.repetitions.value : 1 - const batchSize = stage.batchSize.defined ? stage.batchSize.value : 1 - attemptsInStage = repetitions * batchSize - start += attemptsInStage - index++ - } - - start -= attemptsInStage - index-- - - return { ...domain.stages[index], start } -} - -/** - * Finds the delay to enforce for an attempt given its counter (attempt number) and - * the corresponding stage in the SequentialDelayDomain rate limit. - * - * @param stage IndexedSequentialDelayStage The given stage of the SequentialDelayDomain rate limit, - * extended to include the index of the first attempt in that stage. - * @param counter The current attempt number - */ -const getDelay = (stage: IndexedSequentialDelayStage, counter: number): number => { - const batchSize = stage.batchSize.defined ? stage.batchSize.value : 1 - if ((counter - stage.start) % batchSize === 0) { - return stage.delay - } - return 0 -} diff --git a/packages/phone-number-privacy/common/src/index.ts b/packages/phone-number-privacy/common/src/index.ts deleted file mode 100644 index 1f12254b2b..0000000000 --- a/packages/phone-number-privacy/common/src/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -export * from './domains' -export * from './interfaces' -export { ErrorMessage, WarningMessage } from './interfaces/errors' -export { - PoprfClient, - PoprfCombiner, - PoprfServer, - ThresholdPoprfClient, - ThresholdPoprfServer, -} from './poprf' -export { TestUtils } from './test/index' -export * from './utils/authentication' -export { fetchEnv, fetchEnvOrDefault, toBool, toNum } from './utils/config.utils' -export * from './utils/constants' -export { BlockchainConfig, getContractKit, getContractKitWithAgent } from './utils/contracts' -export * from './utils/input-validation' -export * from './utils/key-version' -export { genSessionID, loggerMiddleware, rootLogger } from './utils/logger' -export * from './utils/responses.utils' diff --git a/packages/phone-number-privacy/common/src/interfaces/endpoints.ts b/packages/phone-number-privacy/common/src/interfaces/endpoints.ts deleted file mode 100644 index 060853c542..0000000000 --- a/packages/phone-number-privacy/common/src/interfaces/endpoints.ts +++ /dev/null @@ -1,72 +0,0 @@ -export enum SignerEndpointCommon { - METRICS = '/metrics', - STATUS = '/status', -} - -export enum SignerEndpointPNP { - PNP_QUOTA = '/quotaStatus', - PNP_SIGN = '/sign', -} - -export enum CombinerEndpointCommon { - STATUS = '/status', -} - -export enum CombinerEndpointPNP { - PNP_QUOTA = '/quotaStatus', - PNP_SIGN = '/sign', - STATUS = '/status', -} - -export enum DomainEndpoint { - DOMAIN_SIGN = '/domain/sign', - DISABLE_DOMAIN = '/domain/disable', - DOMAIN_QUOTA_STATUS = '/domain/quotaStatus', -} - -export type SignerEndpoint = SignerEndpointCommon | SignerEndpointPNP | DomainEndpoint -export const SignerEndpoint = { ...SignerEndpointCommon, ...SignerEndpointPNP, ...DomainEndpoint } - -export type CombinerEndpoint = CombinerEndpointCommon | CombinerEndpointPNP | DomainEndpoint -export const CombinerEndpoint = { - ...CombinerEndpointCommon, - ...CombinerEndpointPNP, - ...DomainEndpoint, -} - -export type Endpoint = SignerEndpoint | CombinerEndpoint -export const Endpoint = { ...SignerEndpoint, ...CombinerEndpoint } - -export function getSignerEndpoint(endpoint: CombinerEndpoint): SignerEndpoint { - switch (endpoint) { - case CombinerEndpoint.DISABLE_DOMAIN: - return SignerEndpoint.DISABLE_DOMAIN - case CombinerEndpoint.DOMAIN_QUOTA_STATUS: - return SignerEndpoint.DOMAIN_QUOTA_STATUS - case CombinerEndpoint.DOMAIN_SIGN: - return SignerEndpoint.DOMAIN_SIGN - case CombinerEndpoint.PNP_QUOTA: - return SignerEndpoint.PNP_QUOTA - case CombinerEndpoint.PNP_SIGN: - return SignerEndpoint.PNP_SIGN - default: - throw new Error(`No corresponding signer endpoint exists for combiner endpoint ${endpoint}`) - } -} - -export function getCombinerEndpoint(endpoint: SignerEndpoint): CombinerEndpoint { - switch (endpoint) { - case SignerEndpoint.DISABLE_DOMAIN: - return CombinerEndpoint.DISABLE_DOMAIN - case SignerEndpoint.DOMAIN_QUOTA_STATUS: - return CombinerEndpoint.DOMAIN_QUOTA_STATUS - case SignerEndpoint.DOMAIN_SIGN: - return CombinerEndpoint.DOMAIN_SIGN - case SignerEndpoint.PNP_QUOTA: - return CombinerEndpoint.PNP_QUOTA - case SignerEndpoint.PNP_SIGN: - return CombinerEndpoint.PNP_SIGN - default: - throw new Error(`No corresponding combiner endpoint exists for signer endpoint ${endpoint}`) - } -} diff --git a/packages/phone-number-privacy/common/src/interfaces/errors.ts b/packages/phone-number-privacy/common/src/interfaces/errors.ts deleted file mode 100644 index 69a9888955..0000000000 --- a/packages/phone-number-privacy/common/src/interfaces/errors.ts +++ /dev/null @@ -1,54 +0,0 @@ -// ERR_# is used for logging so ensure that this is unique when adding a new message -export enum ErrorMessage { - UNKNOWN_ERROR = `CELO_ODIS_ERR_00 Something went wrong`, - DATABASE_UPDATE_FAILURE = `CELO_ODIS_ERR_01 DB_ERR Failed to update database entry`, - DATABASE_INSERT_FAILURE = `CELO_ODIS_ERR_02 DB_ERR Failed to insert database entry`, - DATABASE_GET_FAILURE = `CELO_ODIS_ERR_03 DB_ERR Failed to get database entry`, - KEY_FETCH_ERROR = `CELO_ODIS_ERR_04 INIT_ERR Failed to retrieve key from keystore`, - SIGNATURE_COMPUTATION_FAILURE = `CELO_ODIS_ERR_05 SIG_ERR Failed to compute BLS signature`, - VERIFY_PARITAL_SIGNATURE_ERROR = `CELO_ODIS_ERR_06 SIG_ERR BLS partial signature verification Failure`, - NOT_ENOUGH_PARTIAL_SIGNATURES = `CELO_ODIS_ERR_07 SIG_ERR Not enough partial signatures`, - INCONSISTENT_SIGNER_RESPONSES = `CELO_ODIS_ERR_08 SIG_ERR Inconsistent responses from signers`, - SIGNER_REQUEST_ERROR = `CELO_ODIS_ERR_09 SIG_ERR Failure in signer request`, - TIMEOUT_FROM_SIGNER = `CELO_ODIS_ERR_10 SIG_ERR Timeout from signer`, - FULL_NODE_ERROR = `CELO_ODIS_ERR_11 NODE_ERR Failed to read on-chain state`, - FAILURE_TO_STORE_REQUEST = `CELO_ODIS_ERR_12 DB_ERR Failed to store partial sig request`, - FAILURE_TO_INCREMENT_QUERY_COUNT = `CELO_ODIS_ERR_13 DB_ERR Failed to increment user query count`, - DOMAIN_ALREADY_DISABLED_FAILURE = `CELO_ODIS_ERR_14 DB_ERR Domain is already disabled`, - UNSUPPORTED_DOMAIN = `CELO_ODIS_ERR_15 DOMAIN Domain type is not supported`, - SIGNER_DISABLE_DOMAIN_FAILURE = `CELO_ODIS_ERR_16 DOMAIN Failed to disable domain on a signer`, - THRESHOLD_DISABLE_DOMAIN_FAILURE = `CELO_ODIS_ERR_17 DOMAIN Failed to disable domain on a threshold of signers`, - SIGNER_DOMAIN_QUOTA_STATUS_FAILURE = `CELO_ODIS_ERR_18 DOMAIN Failed to get domain status from signer`, - THRESHOLD_DOMAIN_QUOTA_STATUS_FAILURE = `CELO_ODIS_ERR_19 DOMAIN Failed to get domain quota status from a threshold of signers`, - INVALID_KEY_VERSION_RESPONSE = `CELO_ODIS_ERR_20 SIG_ERR Signer response key version header is invalid`, - INVALID_SIGNER_RESPONSE = `CELO_ODIS_ERR_21 SIG_ERR Signer response body is invalid`, - SIGNER_RESPONSE_FAILED_WITH_OK_STATUS = `CELO_ODIS_ERR_22 SIG_ERR Signer response failed with 200 status`, - THRESHOLD_PNP_QUOTA_STATUS_FAILURE = `CELO_ODIS_ERR_23 SIG_ERR Failed to get PNP quota status from a threshold of signers`, - FAILURE_TO_GET_PERFORMED_QUERY_COUNT = `CELO_ODIS_ERR_24 DB_ERR Failed to read performedQueryCount from signer db`, - FAILURE_TO_GET_TOTAL_QUOTA = `CELO_ODIS_ERR_25 NODE_ERR Failed to read on-chain state to calculate total quota`, - FAILURE_TO_GET_DEK = `CELO_ODIS_ERR_27 NODE_ERR Failed to read user's DEK from full-node`, - CAUGHT_ERROR_IN_ENDPOINT_HANDLER = `CELO_ODIS_ERR_30 Caught error in outer endpoint handler`, - ERROR_AFTER_RESPONSE_SENT = `CELO_ODIS_ERR_31 Error in endpoint thrown after response was already sent`, - SIGNATURE_AGGREGATION_FAILURE = 'CELO_ODIS_ERR_32 SIG_ERR Failed to blind aggregate signature shares', - DATABASE_REMOVE_FAILURE = 'CELO_ODIS_ERR_33 DB_ERR Failed to remove database entries', -} - -export enum WarningMessage { - INVALID_INPUT = `CELO_ODIS_WARN_01 BAD_INPUT Invalid input parameters`, - UNAUTHENTICATED_USER = `CELO_ODIS_WARN_02 BAD_INPUT Missing or invalid authentication`, - EXCEEDED_QUOTA = `CELO_ODIS_WARN_03 QUOTA Requester exceeded service query quota`, - DUPLICATE_REQUEST_TO_GET_PARTIAL_SIG = `CELO_ODIS_WARN_04 BAD_INPUT Attempt to replay partial signature request`, - INCONSISTENT_SIGNER_QUOTA_MEASUREMENTS = `CELO_ODIS_WARN_06 SIGNER Discrepancy found in signers quota measurements`, - MISSING_SESSION_ID = `CELO_ODIS_WARN_07 BAD_INPUT Client did not provide sessionID in request`, - CANCELLED_REQUEST_TO_SIGNER = `CELO_ODIS_WARN_08 SIGNER Cancelled request to signer`, - UNKNOWN_DOMAIN = `CELO_ODIS_WARN_10 BAD_INPUT Provided domain name and version is not recognized`, - DISABLED_DOMAIN = `CELO_ODIS_WARN_11 BAD_INPUT Provided domain is disabled`, - INVALID_KEY_VERSION_REQUEST = `CELO_ODIS_WARN_12 BAD_INPUT Request key version header is invalid`, - API_UNAVAILABLE = `CELO_ODIS_WARN_13 BAD_INPUT API is unavailable`, - INCONSISTENT_SIGNER_DOMAIN_DISABLED_STATES = `CELO_ODIS_WARN_14 SIGNER Discrepency found in signer domain disabled states`, - INVALID_NONCE = `CELO_ODIS_WARN_16 BAD_INPUT SequentialDelayDomain nonce check failed on Signer request`, - SIGNER_RESPONSE_DISCREPANCIES = `CELO_ODIS_WARN_17 SIGNER Discrepancies detected in signer responses`, - INCONSISTENT_SIGNER_QUERY_MEASUREMENTS = `CELO_ODIS_WARN_18 SIGNER Discrepancy found in signers performed query count measurements`, -} - -export type ErrorType = ErrorMessage | WarningMessage diff --git a/packages/phone-number-privacy/common/src/interfaces/index.ts b/packages/phone-number-privacy/common/src/interfaces/index.ts deleted file mode 100644 index 4a68aa366a..0000000000 --- a/packages/phone-number-privacy/common/src/interfaces/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './endpoints' -export * from './errors' -export * from './requests' -export * from './responses' diff --git a/packages/phone-number-privacy/common/src/interfaces/requests.ts b/packages/phone-number-privacy/common/src/interfaces/requests.ts deleted file mode 100644 index d76a125323..0000000000 --- a/packages/phone-number-privacy/common/src/interfaces/requests.ts +++ /dev/null @@ -1,508 +0,0 @@ -import { - EIP712Optional, - eip712OptionalSchema, - eip712OptionalType, - EIP712TypedData, - noString, -} from '@celo/utils/lib/sign-typed-data-utils' -import { verifyEIP712TypedDataSigner } from '@celo/utils/lib/signatureUtils' -import { chain, isRight } from 'fp-ts/lib/Either' -import { pipe } from 'fp-ts/lib/pipeable' -import * as t from 'io-ts' -import { KEY_VERSION_HEADER } from '..' -import { - Domain, - domainEIP712Types, - DomainOptions, - domainOptionsEIP712Types, - isSequentialDelayDomain, - SequentialDelayDomain, - SequentialDelayDomainOptionsSchema, -} from '../domains' - -// Domain request types are only assignable to EIP712Object when using type instead -// of interface. Otherwise the compiler complains about a missing index signature. -// tslint:disable:interface-over-type-literal - -export enum AuthenticationMethod { - WALLET_KEY = 'wallet_key', - ENCRYPTION_KEY = 'encryption_key', -} - -export interface SignMessageRequest { - /** Celo account address. Query is charged against this account's quota. */ - account: string - /** Query message. A blinded elliptic curve point encoded in base64. */ - blindedQueryPhoneNumber: string - /** Authentication method to use for verifying the signature in the Authorization header */ - authenticationMethod?: string - /** Client-specified session ID for the request. */ - sessionID?: string - /** Client-specified version string */ - version?: string -} - -export const SignMessageRequestSchema: t.Type = t.intersection([ - t.type({ - account: t.string, - blindedQueryPhoneNumber: t.string, - }), - t.partial({ - authenticationMethod: t.union([t.string, t.undefined]), - sessionID: t.union([t.string, t.undefined]), - version: t.union([t.string, t.undefined]), - }), -]) - -export interface PnpQuotaRequest { - account: string - /** Authentication method to use for verifying the signature in the Authorization header */ - authenticationMethod?: string - /** Client-specified session ID for the request. */ - sessionID?: string - /** Client-specified version string */ - version?: string -} - -export const PnpQuotaRequestSchema: t.Type = t.intersection([ - t.type({ - account: t.string, - }), - t.partial({ - authenticationMethod: t.union([t.string, t.undefined]), - sessionID: t.union([t.string, t.undefined]), - version: t.union([t.string, t.undefined]), - }), -]) - -export type PhoneNumberPrivacyRequest = SignMessageRequest | PnpQuotaRequest - -export enum DomainRequestTypeTag { - SIGN = 'DomainRestrictedSignatureRequest', - QUOTA = 'DomainQuotaStatusRequest', - DISABLE = 'DisableDomainRequest', -} - -/** - * Domain restricted signature request to get a pOPRF evaluation on the given message in a given - * domain, as specified by CIP-40. - * - * @remarks Concrete request types are created by specifying the type parameter for Domain. If a - * domain has no options, an empty struct should be used. - */ -export type DomainRestrictedSignatureRequest = { - /** Request type tag to ensure this type can be distinguished from other request objects. */ - type: DomainRequestTypeTag.SIGN - /** Domain specification. Selects the PRF domain and rate limiting rules. */ - domain: D - /** - * Domain-specific options. - * Used for inputs relevant to the domain, but not part of the domain string. - * Example: { "authorization": } for an account-restricted domain. - */ - options: DomainOptions - /** Query message. A blinded elliptic curve point encoded in base64. */ - blindedMessage: string - /** Client-specified session ID. */ - sessionID: EIP712Optional -} - -/** - * Request to get the quota status of the given domain. ODIS will respond with the current state - * relevant to calculating quota under the associated rate limiting rules. - * - * Options may be provided for authentication in case the quota state is non-public information. - * E.g. Quota state may reveal whether or not a user has attempted to recover a given account. - * - * @remarks Concrete request types are created by specifying the type parameter for Domain. If a - * domain has no options, an empty struct should be used. - */ -export type DomainQuotaStatusRequest = { - /** Request type tag to ensure this type can be distinguished from other request objects. */ - type: DomainRequestTypeTag.QUOTA - /** Domain specification. Selects the PRF domain and rate limiting rules. */ - domain: D - /** Domain-specific options. */ - options: DomainOptions - /** Client-specified session ID. */ - sessionID: EIP712Optional -} - -/** - * Request to disable a domain such that not further requests for signatures in the given domain - * will be served. Available for domains which need to option to prevent further requests for - * security. - * - * Options may be provided for authentication to prevent unintended parties from disabling a domain. - * - * @remarks Concrete request types are created by specifying the type parameter for Domain. If a - * domain has no options, an empty struct should be used. - */ -export type DisableDomainRequest = { - /** Request type tag to ensure this type can be distinguished from other request objects. */ - type: DomainRequestTypeTag.DISABLE - /** Domain specification. Selects the PRF domain and rate limiting rules. */ - domain: D - /** Domain-specific options. */ - options: DomainOptions - /** Client-specified session ID. */ - sessionID: EIP712Optional -} - -/** Union type of Domain API requests */ -export type DomainRequest = - | DomainRestrictedSignatureRequest - | DomainQuotaStatusRequest - | DisableDomainRequest - -export type OdisRequest = DomainRequest | PhoneNumberPrivacyRequest - -// NOTE: Next three functions are a bit repetitive. An attempt was made to combine them, but the -// type signature got quite complicated. Feel free to attempt it if you are motivated. - -/** Parameterized schema for checking unknown input against DomainRestrictedSignatureRequest */ -export function domainRestrictedSignatureRequestSchema( - domain: t.Type -): t.Type> { - // The schema defined here does most of the work, but does not guarantee consistency between the - // domain and options fields. We wrap the schema below to add a consistency check. - const schema = t.strict({ - domain, - type: t.literal(DomainRequestTypeTag.SIGN), - options: t.unknown, - blindedMessage: t.string, - sessionID: eip712OptionalSchema(t.string), - }) - - const validation = ( - unk: unknown, - ctx: t.Context - ): t.Validation> => - pipe( - schema.validate(unk, ctx), - chain((value: t.TypeOf) => { - if (isSequentialDelayDomain(value.domain)) { - const either = SequentialDelayDomainOptionsSchema.validate(value.options, ctx) - if (isRight(either)) { - return t.success(value as DomainRestrictedSignatureRequest) - } - - return t.failure(unk, ctx, 'options type does not match domain type') - } - - // canary provides a compile-time check that all subtypes of Domain have branches. If a case - // was missed, then an error will report that domain cannot be assigned to type `never`. - const canary = (x: never) => x - canary(value.domain) - throw new Error('Implementation error: validated domain is not of any known type') - }) - ) - - return new t.Type, DomainRestrictedSignatureRequest>( - `DomainRestrictedSignatureRequestSchema<${domain.name}>`, - (unk: unknown): unk is DomainRestrictedSignatureRequest => isRight(validation(unk, [])), - validation, - (req: DomainRestrictedSignatureRequest) => req - ) -} - -/** Parameterized schema for checking unknown input against DomainQuotaStatusRequest */ -export function domainQuotaStatusRequestSchema( - domain: t.Type -): t.Type> { - // The schema defined here does most of the work, but does not guarantee consistency between the - // domain and options fields. We wrap the schema below to add a consistency check. - const schema = t.strict({ - domain, - type: t.literal(DomainRequestTypeTag.QUOTA), - options: t.unknown, - sessionID: eip712OptionalSchema(t.string), - }) - - const validation = (unk: unknown, ctx: t.Context): t.Validation> => - pipe( - schema.validate(unk, ctx), - chain((value: t.TypeOf) => { - if (isSequentialDelayDomain(value.domain)) { - const either = SequentialDelayDomainOptionsSchema.validate(value.options, ctx) - if (isRight(either)) { - return t.success(value as DomainQuotaStatusRequest) - } - - return t.failure(unk, ctx, 'options type does not match domain type') - } - - // canary provides a compile-time check that all subtypes of Domain have branches. If a case - // was missed, then an error will report that domain cannot be assigned to type `never`. - const canary = (x: never) => x - canary(value.domain) - throw new Error('Implementation error: validated domain is not of any known type') - }) - ) - - return new t.Type, DomainQuotaStatusRequest>( - `DomainQuotaStatusRequestSchema<${domain.name}>`, - (unk: unknown): unk is DomainQuotaStatusRequest => isRight(validation(unk, [])), - validation, - (req: DomainQuotaStatusRequest) => req - ) -} - -/** Parameterized schema for checking unknown input against DisableDomainRequest */ -export function disableDomainRequestSchema( - domain: t.Type -): t.Type> { - // The schema defined here does most of the work, but does not guarantee consistency between the - // domain and options fields. We wrap the schema below to add a consistency check. - const schema = t.strict({ - domain, - type: t.literal(DomainRequestTypeTag.DISABLE), - options: t.unknown, - sessionID: eip712OptionalSchema(t.string), - }) - - const validation = (unk: unknown, ctx: t.Context): t.Validation> => - pipe( - schema.validate(unk, ctx), - chain((value: t.TypeOf) => { - if (isSequentialDelayDomain(value.domain)) { - const either = SequentialDelayDomainOptionsSchema.validate(value.options, ctx) - if (isRight(either)) { - return t.success(value as DisableDomainRequest) - } - - return t.failure(unk, ctx, 'options type does not match domain type') - } - - // canary provides a compile-time check that all subtypes of Domain have branches. If a case - // was missed, then an error will report that domain cannot be assigned to type `never`. - const canary = (x: never) => x - canary(value.domain) - throw new Error('Implementation error: validated domain is not of any known type') - }) - ) - - return new t.Type, DisableDomainRequest>( - `DisableDomainRequestSchema<${domain.name}>`, - (unk: unknown): unk is DisableDomainRequest => isRight(validation(unk, [])), - validation, - (req: DisableDomainRequest) => req - ) -} - -/** Wraps the signature request as an EIP-712 typed data structure for hashing and signing */ -export function domainRestrictedSignatureRequestEIP712( - request: DomainRestrictedSignatureRequest -): EIP712TypedData { - const domainTypes = domainEIP712Types(request.domain) - const optionsTypes = domainOptionsEIP712Types(request.domain) - return { - types: { - DomainRestrictedSignatureRequest: [ - { name: 'type', type: 'string' }, - { name: 'blindedMessage', type: 'string' }, - { name: 'domain', type: domainTypes.primaryType }, - { name: 'options', type: optionsTypes.primaryType }, - { name: 'sessionID', type: 'Optional' }, - ], - ...domainTypes.types, - ...optionsTypes.types, - ...eip712OptionalType('string'), - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - ], - }, - primaryType: 'DomainRestrictedSignatureRequest', - domain: { - name: 'ODIS Domain Restricted Signature Request', - version: '1', - }, - message: request, - } -} - -/** Wraps the domain quota request as an EIP-712 typed data structure for hashing and signing */ -export function domainQuotaStatusRequestEIP712( - request: DomainQuotaStatusRequest -): EIP712TypedData { - const domainTypes = domainEIP712Types(request.domain) - const optionsTypes = domainOptionsEIP712Types(request.domain) - return { - types: { - DomainQuotaStatusRequest: [ - { name: 'type', type: 'string' }, - { name: 'domain', type: domainTypes.primaryType }, - { name: 'options', type: optionsTypes.primaryType }, - { name: 'sessionID', type: 'Optional' }, - ], - ...domainTypes.types, - ...optionsTypes.types, - ...eip712OptionalType('string'), - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - ], - }, - primaryType: 'DomainQuotaStatusRequest', - domain: { - name: 'ODIS Domain Quota Status', - version: '1', - }, - message: request, - } -} - -/** Wraps the disable domain request as an EIP-712 typed data structure for hashing and signing */ -export function disableDomainRequestEIP712( - request: DisableDomainRequest -): EIP712TypedData { - const domainTypes = domainEIP712Types(request.domain) - const optionsTypes = domainOptionsEIP712Types(request.domain) - return { - types: { - DisableDomainRequest: [ - { name: 'type', type: 'string' }, - { name: 'domain', type: domainTypes.primaryType }, - { name: 'options', type: optionsTypes.primaryType }, - { name: 'sessionID', type: 'Optional' }, - ], - ...domainTypes.types, - ...optionsTypes.types, - ...eip712OptionalType('string'), - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - ], - }, - primaryType: 'DisableDomainRequest', - domain: { - name: 'ODIS Disable Domain Request', - version: '1', - }, - message: request, - } -} - -/** - * Generic function to verify the signature on a Domain API request. - * - * @remarks Passing in the builder allows the caller to handle the differences of EIP-712 types - * between request types. Requests cannot be fully differentiated at runtime. In particular, - * DomainQuotaStatusRequest and DisableDomainRequest are indistinguishable at runtime. - * - * @privateRemarks Function is currently defined explicitly in terms of SequentialDelayDomain. It - * should be generalized to other authenticated domain types as they are standardized. - */ -function verifyRequestSignature>( - typedDataBuilder: (request: R) => EIP712TypedData, - request: R -): boolean { - // If the address field is undefined, then this domain is unauthenticated. - // Return false as the signature cannot be checked. - if (!request.domain.address.defined) { - return false - } - const signer = request.domain.address.value - - // If not signature is provided, return false. - if (!request.options.signature.defined) { - return false - } - const signature = request.options.signature.value - - // Requests are signed over the message excluding the signature. CIP-40 specifies that the - // signature in the signed message should be the zero value. When the signature type is - // EIP712Optional, this is { defined: false, value: "" } (i.e. `noString`) - const message: R = { - ...request, - options: { - ...request.options, - signature: noString, - }, - } - - // Build the typed data then return the result of signature verification. - const typedData = typedDataBuilder(message) - return verifyEIP712TypedDataSigner(typedData, signature, signer) -} - -/** - * Verifies the authentication (e.g. client signature) over a domain signature request. - * If the domain is unauthenticated, this function returns false. - * - * @remarks As specified in CIP-40, the signed message is the full request interpreted as EIP-712 - * typed data with the signature field in the domain options set to its zero value (i.e. It is set - * to the undefined value for type EIP712Optional). - */ -export function verifyDomainRestrictedSignatureRequestAuthenticity( - request: DomainRestrictedSignatureRequest -): boolean { - return verifyRequestSignature(domainRestrictedSignatureRequestEIP712, request) -} - -/** - * Verifies the authentication (e.g. client signature) over a domain status request. - * If the domain is unauthenticated, this function returns false. - * - * @remarks As specified in CIP-40, the signed message is the full request interpreted as EIP-712 - * typed data with the signature field in the domain options set to its zero value (i.e. It is set - * to the undefined value for type EIP712Optional). - */ -export function verifyDomainQuotaStatusRequestAuthenticity( - request: DomainQuotaStatusRequest -): boolean { - return verifyRequestSignature(domainQuotaStatusRequestEIP712, request) -} - -/** - * Verifies the authentication (e.g. client signature) over a disable domain request. - * If the domain is unauthenticated, this function returns false. - * - * @remarks As specified in CIP-40, the signed message is the full request interpreted as EIP-712 - * typed data with the signature field in the domain options set to its zero value (i.e. It is set - * to the undefined value for type EIP712Optional). - */ -export function verifyDisableDomainRequestAuthenticity( - request: DisableDomainRequest -): boolean { - return verifyRequestSignature(disableDomainRequestEIP712, request) -} - -interface PnpAuthHeader { - Authorization: string -} - -interface KeyVersionHeader { - [KEY_VERSION_HEADER]?: string -} - -export type DomainRestrictedSignatureRequestHeader = KeyVersionHeader -export type DisableDomainRequestHeader = undefined -export type DomainQuotaStatusRequestHeader = undefined - -export type DomainRequestHeader = - R extends DomainRestrictedSignatureRequest - ? DomainRestrictedSignatureRequestHeader - : never | R extends DisableDomainRequest - ? DisableDomainRequestHeader - : never | R extends DomainQuotaStatusRequest - ? DomainQuotaStatusRequestHeader - : never - -export type SignMessageRequestHeader = KeyVersionHeader & PnpAuthHeader - -export type PnpQuotaRequestHeader = PnpAuthHeader - -export type PhoneNumberPrivacyRequestHeader = - R extends SignMessageRequest - ? SignMessageRequestHeader - : never | R extends PnpQuotaRequest - ? PnpQuotaRequestHeader - : never - -export type OdisRequestHeader = R extends DomainRequest - ? DomainRequestHeader - : never | R extends PhoneNumberPrivacyRequest - ? PhoneNumberPrivacyRequestHeader - : never diff --git a/packages/phone-number-privacy/common/src/interfaces/responses.ts b/packages/phone-number-privacy/common/src/interfaces/responses.ts deleted file mode 100644 index 8c9c7d2b50..0000000000 --- a/packages/phone-number-privacy/common/src/interfaces/responses.ts +++ /dev/null @@ -1,241 +0,0 @@ -import * as t from 'io-ts' -import { - DisableDomainRequest, - DomainQuotaStatusRequest, - DomainRequest, - DomainRestrictedSignatureRequest, - OdisRequest, - PhoneNumberPrivacyRequest, - PnpQuotaRequest, - SignMessageRequest, -} from '.' -import { Domain, DomainState } from '../domains' - -// Phone Number Privacy -export interface PnpQuotaStatus { - performedQueryCount: number - // all time total quota - totalQuota: number -} - -const PnpQuotaStatusSchema: t.Type = t.type({ - performedQueryCount: t.number, - totalQuota: t.number, -}) - -export interface SignMessageResponseSuccess extends PnpQuotaStatus { - success: true - version: string - signature: string - warnings?: string[] -} - -export interface SignMessageResponseFailure { - success: false - version: string - error: string - // These fields are occasionally provided by the signer but not the combiner - // because the combiner separates failure/success responses before processing states. - // => If the signer response fails, then it's irrelevant if that signer returned quota values, - // since these won't be used in the quota calculation anyways. - // Changing this is more involved; TODO(future) https://github.com/celo-org/celo-monorepo/issues/9826 - performedQueryCount?: number - totalQuota?: number -} - -export type SignMessageResponse = SignMessageResponseSuccess | SignMessageResponseFailure - -export const SignMessageResponseSchema: t.Type = t.union([ - t.intersection([ - t.type({ - success: t.literal(true), - version: t.string, - signature: t.string, - }), - t.partial({ - warnings: t.union([t.array(t.string), t.undefined]), - }), - PnpQuotaStatusSchema, - ]), - t.intersection([ - t.type({ - success: t.literal(false), - version: t.string, - error: t.string, - }), - t.partial({ - performedQueryCount: t.union([t.number, t.undefined]), - totalQuota: t.union([t.number, t.undefined]), - }), - ]), -]) - -export interface PnpQuotaResponseSuccess extends PnpQuotaStatus { - success: true - version: string - warnings?: string[] -} - -export interface PnpQuotaResponseFailure { - success: false - version: string - error: string -} - -export type PnpQuotaResponse = PnpQuotaResponseSuccess | PnpQuotaResponseFailure - -export const PnpQuotaResponseSchema: t.Type = t.union([ - t.intersection([ - t.type({ - success: t.literal(true), - version: t.string, - }), - t.partial({ - warnings: t.union([t.array(t.string), t.undefined]), - }), - PnpQuotaStatusSchema, - ]), - t.type({ - success: t.literal(false), - version: t.string, - error: t.string, - }), -]) - -// prettier-ignore -export type PhoneNumberPrivacyResponse< - R extends PhoneNumberPrivacyRequest = PhoneNumberPrivacyRequest -> = - | R extends SignMessageRequest ? SignMessageResponse : never - | R extends PnpQuotaRequest ? PnpQuotaResponse : never - -// Domains - -export interface DomainRestrictedSignatureResponseSuccess { - success: true - version: string - signature: string - status: DomainState -} - -export interface DomainRestrictedSignatureResponseFailure { - success: false - version: string - error: string - status?: DomainState -} - -export type DomainRestrictedSignatureResponse = - | DomainRestrictedSignatureResponseSuccess - | DomainRestrictedSignatureResponseFailure - -export interface DomainQuotaStatusResponseSuccess { - success: true - version: string - status: DomainState -} - -export interface DomainQuotaStatusResponseFailure { - success: false - version: string - error: string -} - -export type DomainQuotaStatusResponse = - | DomainQuotaStatusResponseSuccess - | DomainQuotaStatusResponseFailure - -export interface DisableDomainResponseSuccess { - success: true - version: string - status: DomainState -} - -export interface DisableDomainResponseFailure { - success: false - version: string - error: string -} - -export type DisableDomainResponse = - | DisableDomainResponseSuccess - | DisableDomainResponseFailure - -// prettier-ignore -export type DomainResponse< - R extends DomainRequest = DomainRequest -> = - | R extends DomainRestrictedSignatureRequest ? DomainRestrictedSignatureResponse : never - | R extends DomainQuotaStatusRequest ? DomainQuotaStatusResponse : never - | R extends DisableDomainRequest ? DisableDomainResponse : never - -export function domainRestrictedSignatureResponseSchema( - state: t.Type> -): t.Type> { - return t.union([ - t.type({ - success: t.literal(true), - version: t.string, - signature: t.string, - status: state, - }), - t.intersection([ - t.type({ - success: t.literal(false), - version: t.string, - error: t.string, - }), - t.partial({ - status: t.union([state, t.undefined]), - }), - ]), - ]) -} - -export function domainQuotaStatusResponseSchema( - state: t.Type> -): t.Type> { - return t.union([ - t.type({ - success: t.literal(true), - version: t.string, - status: state, - }), - t.type({ - success: t.literal(false), - version: t.string, - error: t.string, - }), - ]) -} - -export function disableDomainResponseSchema( - state: t.Type> -): t.Type> { - return t.union([ - t.type({ - success: t.literal(true), - version: t.string, - status: state, - }), - t.type({ - success: t.literal(false), - version: t.string, - error: t.string, - }), - ]) -} - -// General - -// prettier-ignore -export type OdisResponse = - | R extends DomainRequest ? DomainResponse : never - | R extends PhoneNumberPrivacyRequest ? PhoneNumberPrivacyResponse : never - -export type SuccessResponse = OdisResponse & { - success: true -} -export type FailureResponse = OdisResponse & { - success: false -} diff --git a/packages/phone-number-privacy/common/src/poprf.ts b/packages/phone-number-privacy/common/src/poprf.ts deleted file mode 100644 index 633b469af5..0000000000 --- a/packages/phone-number-privacy/common/src/poprf.ts +++ /dev/null @@ -1,246 +0,0 @@ -// Note that this import is only ever used for its type information. As a result, it will not be -// included in the compiled JavaScript or result in an import at runtime. -// https://www.typescriptlang.org/docs/handbook/modules.html#optional-module-loading-and-other-advanced-loading-scenarios -import * as POPRF from '@celo/poprf' -import { randomBytes } from 'crypto' - -/** - * @module - * This module provides interfaces for the Pith POPRF. It allows the construction of client, server, - * and combiner objects that wrap that package the required functionality. - * - * A partially-oblivious PRF (POPRF) is a protocol between a client and a server to evaluate the - * keyed pseudo-random function F(k, t, m). The client provides the tag input, t, and message input, - * m. The server provides the secret key input k. During the exchange the server learns the - * client-provided tag, but gains no other information. In particular, they learn nothing about the - * message. The client learns the output of the PRF function F, but no other information about the - * secret key held by the server. - * - * Implementation of the POPRF can be found in the following repository: - * https://github.com/celo-org/celo-poprf-rs - */ - -// tslint:disable: max-classes-per-file - -let _poprf: typeof POPRF | undefined - -/** - * Lazy loading function for the POPRF WASM implementation dependency. - * - * @remarks @celo/poprf version 0.1.x is compiled to a WASM is around 320 kB. Note that this must be - * loaded into memory at runtime. In order to avoid that cost, both on-disk and in memory, for users - * that do not need the POPRF functionality, it is loaded lazily, and included only as a dev - * dependency for clients that do not need it. If a package wants to utilize the POPRF - * functionality, it should add @celo/poprf to its dependencies (i.e. package.json). - */ -function poprf(): typeof POPRF { - // TODO: This will only initially work in Node environments. If we want to have this work in - // ReactNative and browser environments, some work will need to be done in @celo/poprf or here. - if (_poprf === undefined) { - try { - _poprf = require('@celo/poprf') - } catch { - throw new Error('@celo/poprf not available. Add it to the package.json') - } - } - return _poprf! -} - -/** Client for an instance of the POPRF protocol interacting with a POPRF service. */ -export class PoprfClient { - /** Blinded message to be sent to the POPRF service for evaluation */ - public readonly blindedMessage: Buffer - - /** Secret blinding factor used for blinding and response verification. */ - protected readonly blindingFactor: Buffer - - /** - * Constructs POPRF client state, blinding the given message and saving the public key, blinding - * factor, and tag for use in verification and unbinding of the response. - * - * @remarks Note that this client represents the client-side of a single protocol exchange. - * - * @param publicKey Public key for the POPRF service for use in verification. - * @param tag A plaintext tag which will be sent to the service along with the message. - * @param message A plaintext message which you want to blind and send to the POPRF service. - * @param seed Seed for the blinding factor. Provided if deterministic blinding is needed. - * Note that, by design, if the same seed and message is used twice, the blinded message will be - * the same. This allows for linking between the two blinded messages and so only should be used - * if this is intended (e.g. to provide for retries of requests without consuming quota). - */ - constructor( - readonly publicKey: Uint8Array, - readonly tag: Uint8Array, - readonly message: Uint8Array, - readonly seed?: Uint8Array - ) { - const blinded = poprf().blindMsg(message, seed ?? randomBytes(32)) - - // Save the blinding factor to the class state so that unblind can use it. - this.blindingFactor = Buffer.from(blinded.blindingFactor) - this.blindedMessage = Buffer.from(blinded.blindedMessage) - } - - /** - * Given a blinded evaluation response, unblind and verify the evaluation, returning the result. - * - * @remarks Note that this function expects a complete/aggregated response, and not a partial - * response as is returned by an individual server in a threshold service implementation. If the - * client wishes to unblind and verify partial responses, they will need to use the - * ThresholdPoprfClient. - * - * @param response A blinded evaluation response. - * @returns a buffer with the final POPRF output. - * - * @throws If the given response is invalid or cannot be verified against the public key, tag, and - * blinding state present in this client. - */ - public unblindResponse(response: Uint8Array): Buffer { - return Buffer.from(poprf().unblindResp(this.publicKey, this.blindingFactor, this.tag, response)) - } -} - -/** - * Combiner for an instance of the POPRF protocol acting as a relayer between the client and - * threshold service operators. A combiner effectively combines a set of service operators to appear - * as a single service. - * - * @remarks In the Pith POPRF protocol, verification occurs as part of the unblinding process and - * therefore only the client and determine is a given response is valid. As a result, the combiner - * cannot determine whether the responses from the service as correct, as long as they are - * well-formed. - */ -export class PoprfCombiner { - constructor(readonly threshold: number) { - if (threshold % 1 !== 0) { - throw new Error('POPRF threshold must be an integer') - } - } - - /** - * If there are enough responses provided, aggregates the collection of blind partial evaluations - * to a single blind threshold evaluation. - * - * @param response An array of blinded partial evaluation responses. - * @remarks Does not verify any of the responses. Verification only occurs during unblinding. - * - * @returns A buffer with a blind aggregated POPRF evaluation response, or undefined if there are - * less than the threshold number of responses provided. - */ - public blindAggregate(blindedResponses: Uint8Array[]): Buffer | undefined { - if (blindedResponses.length < this.threshold) { - return undefined - } - - return Buffer.from( - poprf().blindAggregate(this.threshold, Buffer.concat(blindedResponses.map(Buffer.from))) - ) - } - - /** - * If there are enough responses provided, aggregates the collection of partial evaluations - * to a single POPRF evaluation. - * - * @param response An array of partial evaluation responses. - * @returns A buffer with a POPRF evaluation, or undefined if there are less than the threshold - * number of responses provided. - */ - public aggregate(responses: Uint8Array[]): Buffer | undefined { - if (responses.length < this.threshold) { - return undefined - } - - return Buffer.from(poprf().aggregate(this.threshold, Buffer.concat(responses.map(Buffer.from)))) - } -} - -/** - * Client for interacting with a threshold implementation of the POPRF service without a combiner. - * - * @privateRemarks - * TODO Combine this class with the functionality from the combiner to create a POPRF client - * that can handle expunging bad partial evaluations from a set of responses. - */ -export class ThresholdPoprfClient extends PoprfClient { - /** - * Constructs POPRF client state, blinding the given message and saving the public keys, blinding - * factor, and tag for use in verification and unbinding of the response. - * - * Note that this client represents the client-side of a single protocol exchange. - * - * @param publicKey Public key for the POPRF service for use in verification. - * @param polynomial Public key polynomial for the individual POPRF servers for use in verification. - * @param tag A plaintext tag which will be sent to the service along with the message. - * @param message A plaintext message which you want to blind and send to the POPRF service. - * @param seed Seed for the blinding factor. Provided if deterministic blinding is needed. - * Note that, by design, if the same seed and message is used twice, the blinded message will be - * the same. This allows for linking between the two blinded messages and so only should be used - * if this is intended (e.g. to provide for retries of requests without consuming quota). - */ - constructor( - readonly publicKey: Uint8Array, - readonly polynomial: Uint8Array, - readonly tag: Uint8Array, - readonly message: Uint8Array, - readonly seed?: Uint8Array - ) { - super(publicKey, tag, message, seed) - } - - /** - * Given a blinded partial evaluation response, unblind and verify the evaluation share, returning the result. - * - * @param response A blinded partial evaluation response. - * @returns a buffer with unblinded partial evaluation. - * - * @throws If the given response is invalid or cannot be verified against the public key, tag, and - * blinding state present in this client. - */ - public unblindPartialResponse(response: Uint8Array): Buffer { - return Buffer.from( - poprf().unblindPartialResp(this.polynomial, this.blindingFactor, this.tag, response) - ) - } -} - -/** - * Server for the POPRF protocol for answering queries from clients. - * - * @remarks Note that, unlike the client, the server object is stateless and may be used for - * multiple protocol exchanges, including being used concurrently. - */ -export class PoprfServer { - constructor(readonly privateKey: Uint8Array) {} - - /** - * Evaluates the POPRF function over the tag and blinded message with the (complete) private key - * - * @param tag plaintext tag buffer to be combined with the blinded message in the POPRF. - * - * @returns a serialized blinded evaluation response. - */ - public blindEval(tag: Uint8Array, blindedMessage: Uint8Array): Buffer { - return Buffer.from(poprf().blindEval(this.privateKey, tag, blindedMessage)) - } -} - -/** - * Server for a threshold implementation of the POPRF protocol for answering queries from clients. - * - * @remarks Note that, unlike the client, the server object is stateless and may be used for - * multiple protocol exchanges, including being used concurrently. - */ -export class ThresholdPoprfServer { - constructor(readonly privateKeyShare: Uint8Array) {} - - /** - * Evaluates the POPRF function over the tag and blinded message with the private key share. - * - * @param tag plaintext tag buffer to be combined with the blinded message in the POPRF. - * - * @returns a serialized blinded partial evaluation response. - */ - public blindPartialEval(tag: Uint8Array, blindedMessage: Uint8Array): Buffer { - return Buffer.from(poprf().blindPartialEval(this.privateKeyShare, tag, blindedMessage)) - } -} diff --git a/packages/phone-number-privacy/common/src/test/index.ts b/packages/phone-number-privacy/common/src/test/index.ts deleted file mode 100644 index 63c1c20597..0000000000 --- a/packages/phone-number-privacy/common/src/test/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as Utils from './utils' -import * as Values from './values' - -export const TestUtils = { - Utils, - Values, -} diff --git a/packages/phone-number-privacy/common/src/test/utils.ts b/packages/phone-number-privacy/common/src/test/utils.ts deleted file mode 100644 index eea6164b72..0000000000 --- a/packages/phone-number-privacy/common/src/test/utils.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { privateKeyToAddress } from '@celo/utils/lib/address' -import { serializeSignature, Signature, signMessage } from '@celo/utils/lib/signatureUtils' -import BigNumber from 'bignumber.js' -import { - AuthenticationMethod, - PhoneNumberPrivacyRequest, - PnpQuotaRequest, - SignMessageRequest, -} from '../interfaces' -import { signWithRawKey } from '../utils/authentication' -import { genSessionID } from '../utils/logger' - -export interface AttestationsStatus { - isVerified: boolean - numAttestationsRemaining: number - total: number - completed: number -} - -export function createMockAttestation(getVerifiedStatus: jest.Mock) { - return { - getVerifiedStatus, - } -} - -export function createMockToken(balanceOf: jest.Mock) { - return { - balanceOf, - } -} - -export function createMockAccounts( - getWalletAddress: jest.Mock, - getDataEncryptionKey: jest.Mock -) { - return { - getWalletAddress, - getDataEncryptionKey, - } -} - -// Take in jest.Mock to enable individual tests to spy on function calls -// and more easily set return values -export function createMockOdisPayments(totalPaidCUSDFunc: jest.Mock) { - return { - totalPaidCUSD: totalPaidCUSDFunc, - } -} - -export function createMockContractKit( - c: { [contractName in ContractRetrieval]?: any }, - mockWeb3?: any -) { - const contracts: any = {} - for (const t of Object.keys(c)) { - contracts[t] = jest.fn(() => c[t as ContractRetrieval]) - } - - return { - contracts, - registry: { - addressFor: async () => 1000, - }, - connection: mockWeb3 ?? createMockConnection(mockWeb3), - } -} - -export function createMockConnection(mockWeb3: any) { - return { - web3: mockWeb3, - getTransactionCount: jest.fn(() => mockWeb3.eth.getTransactionCount()), - getBlockNumber: jest.fn(() => { - return mockWeb3.eth.getBlockNumber() - }), - } -} - -export enum ContractRetrieval { - getStableToken = 'getStableToken', - getGoldToken = 'getGoldToken', - getAccounts = 'getAccounts', - getOdisPayments = 'getOdisPayments', -} - -export function createMockWeb3(txCount: number, blockNumber: number) { - return { - eth: { - getTransactionCount: jest.fn(() => txCount), - getBlockNumber: jest.fn(() => blockNumber), - }, - } -} - -export async function replenishQuota(account: string, contractKit: any) { - const goldToken = await contractKit.contracts.getGoldToken() - const selfTransferTx = goldToken.transfer(account, 1) - await selfTransferTx.sendAndWaitForReceipt({ from: account }) -} - -export async function registerWalletAddress( - accountAddress: string, - walletAddress: string, - walletAddressPk: string, - contractKit: any -) { - const accounts = await contractKit.contracts.getAccounts() - const pop = await accounts.generateProofOfKeyPossessionLocally( - accountAddress, - walletAddress, - walletAddressPk - ) - await accounts - .setWalletAddress(walletAddress, pop as Signature) - .sendAndWaitForReceipt({ from: accountAddress } as any) -} - -export function getPnpQuotaRequest( - account: string, - authenticationMethod?: string -): PnpQuotaRequest { - return { - account, - authenticationMethod, - sessionID: genSessionID(), - } -} - -export function getPnpSignRequest( - account: string, - blindedQueryPhoneNumber: string, - authenticationMethod?: string -): SignMessageRequest { - return { - account, - blindedQueryPhoneNumber, - authenticationMethod, - sessionID: genSessionID(), - } -} - -export function getPnpRequestAuthorization(req: PhoneNumberPrivacyRequest, pk: string) { - const msg = JSON.stringify(req) - if (req.authenticationMethod === AuthenticationMethod.ENCRYPTION_KEY) { - return signWithRawKey(JSON.stringify(req), pk) - } - const account = privateKeyToAddress(pk) - return serializeSignature(signMessage(msg, pk, account)) -} diff --git a/packages/phone-number-privacy/common/src/test/values.ts b/packages/phone-number-privacy/common/src/test/values.ts deleted file mode 100644 index e92546f059..0000000000 --- a/packages/phone-number-privacy/common/src/test/values.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { PhoneNumberUtils } from '@celo/phone-utils' -import { normalizeAddressWith0x, privateKeyToAddress } from '@celo/utils/lib/address' - -export const mockAccount = '0x0000000000000000000000000000000000007E57' -export const mockPhoneNumber = '+14155556666' -export const mockContractAddress = '0x000000000000000000000000000000000000CE10' - -export const PRIVATE_KEY1 = '535029bfb19fe5440dbd549b88fbf5ee847b059485e4eafc2a3e3bdfbf9b31ac' -export const ACCOUNT_ADDRESS1 = normalizeAddressWith0x(privateKeyToAddress(PRIVATE_KEY1)) -export const PRIVATE_KEY2 = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890fdeccc' -export const ACCOUNT_ADDRESS2 = privateKeyToAddress(PRIVATE_KEY2) -export const PRIVATE_KEY3 = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890fffff1d' -export const ACCOUNT_ADDRESS3 = normalizeAddressWith0x(privateKeyToAddress(PRIVATE_KEY3)) -export const PHONE_NUMBER = '+15555555555' -export const IDENTIFIER = PhoneNumberUtils.getPhoneHash(PHONE_NUMBER) -export const BLINDING_FACTOR = Buffer.from('0IsBvRfkBrkKCIW6HV0/T1zrzjQSe8wRyU3PKojCnww=', 'base64') -// BLINDED_PHONE_NUMBER value dependent on PHONE_NUMBER AND BLINDING_FACTOR -// hardcoding to avoid importing blind_threshols_bls library -export const BLINDED_PHONE_NUMBER = - 'n/I9srniwEHm5o6t3y0tTUB5fn7xjxRrLP1F/i8ORCdqV++WWiaAzUo3GA2UNHiB' -export const DEK_PUBLIC_KEY = '0x026063780c81991c032fb4fa7485c6607b7542e048ef85d08516fe5c4482360e4b' -export const DEK_PRIVATE_KEY = '0xc2bbdabb440141efed205497a41d5fb6114e0435fd541e368dc628a8e086bfee' - -// Public keys are expected to be in base64 -export const PNP_DEV_ODIS_PUBLIC_KEY = - 'HzMTasAppwLrBBCWvZ7wncnDaN3lKpcoZr3q/wiW+FlrdKt639cxi7o4UnWZdoQA30S8q2a884Q8F6LOg4vNWouhY0wYMU/wVlp8dpkFuKj7onqGv0xssi34nhut/iuB' -export const PNP_DEV_SIGNER_PRIVATE_KEY = - '00000000dd0005bf4de5f2f052174f5cf58dae1af1d556c7f7f85d6fb3656e1d0f10720f' -export const PNP_DEV_ODIS_POLYNOMIAL = - '01000000000000001f33136ac029a702eb041096bd9ef09dc9c368dde52a972866bdeaff0896f8596b74ab7adfd7318bba38527599768400df44bcab66bcf3843c17a2ce838bcd5a8ba1634c18314ff0565a7c769905b8a8fba27a86bf4c6cb22df89e1badfe2b81' - -// Public keys are expected to be in base64 -export const DOMAINS_DEV_ODIS_PUBLIC_KEY = - 'CyJK6fkM0ZRILiW0h85LFev4BbMcLH1RBX5I9BNDgwX5jM74kv8+FjFZuJ1C4P0ADU1fuPGXXQg+wAGCclUD+BCza6ItIxSYmwsZ4ie1Iw1/pdTcwPJJlXwYwcDo+LKA' -export const DOMAINS_DEV_SIGNER_PRIVATE_KEY = - '01000000f0c2d6231c9ed833da9478cbfd6e4970fcd893e156973862f6d286e7e1f6d904' -export const DOMAINS_DEV_ODIS_POLYNOMIAL = - '01000000000000000b224ae9f90cd194482e25b487ce4b15ebf805b31c2c7d51057e48f413438305f98ccef892ff3e163159b89d42e0fd000d4d5fb8f1975d083ec00182725503f810b36ba22d2314989b0b19e227b5230d7fa5d4dcc0f249957c18c1c0e8f8b280' - -// Generated with 2/3 ratio - -export const PNP_THRESHOLD_DEV_PUBKEY_V1 = - '61aeuHAdgxoKn/5d8yXu0qx/VpPHWMAqrVgEAJ/MpC7Oc/f1YLPiN7YKaw9eDWUBUWs4sPn6IN2UTGbt95jP6nO8IymD4IhbBONjLcElsq1jwTZ2cjuTHV9obSyDFl2B' -export const PNP_THRESHOLD_DEV_PK_SHARE_1_V1 = - '000000000e7e1a2fad3b54deb2b1b32cf4c7b084842d50bbb5c6143b9d9577d16e050f03' -export const PNP_THRESHOLD_DEV_PK_SHARE_2_V1 = - '01000000e43f10f7778e238e1ed58d5fad9363d7439d2b5a8eeda6073d68ba87c0b10011' -export const PNP_THRESHOLD_DEV_PK_SHARE_3_V1 = - '02000000b90106bf4261e13389f867c267e86bd0015dcf9c48c784738695d0a3b3f8460c' -export const PNP_THRESHOLD_DEV_POLYNOMIAL_V1 = - '0200000000000000eb569eb8701d831a0a9ffe5df325eed2ac7f5693c758c02aad5804009fcca42ece73f7f560b3e237b60a6b0f5e0d6501516b38b0f9fa20dd944c66edf798cfea73bc232983e0885b04e3632dc125b2ad63c13676723b931d5f686d2c83165d817aaff1f84d0b008ad218eff19db698f343168cf931ba8347640123a2f826f62b66ff084273f494d4647758e9a9f889009d573705824a0e74e1f49ed234462058e53bbb4fef370b55f78da89df070c661782a84239b8c7623d09e34b9f91f7781' - -// Note: The pubkey doesn't change with a resharing, so normally the different key versions would have the same pubkey. -// We generated these key versions independently (not through resharing), since that is sufficient to test the key rotation logic - -export const PNP_THRESHOLD_DEV_PUBKEY_V2 = - '2ckOWP3qphyao1R4s8VHbVRdenGcFsgskQh5eCMqAwAziJzQAZ6Wo9CFD30YhhoA6B91QFIQaqfDvdblNeOtMDsmIKTDFtxZjg+cZZtQzrCTLU2owWEEb8RPJc8F3ekA' -export const PNP_THRESHOLD_DEV_PK_SHARE_1_V2 = - '0000000087c722e1338395b942d8332328795a46c718baeb8fef9e5c63111d495469c50e' -export const PNP_THRESHOLD_DEV_PK_SHARE_2_V2 = - '01000000e4efa9b60743f8188a68d35663d877143ad1726931eaa9af168fc86472eafd0d' -export const PNP_THRESHOLD_DEV_PK_SHARE_3_V2 = - '020000004118318cdb025b78d1f8728a9e3795e2ac892be7d2e4b402ca0c7480906b360d' -export const PNP_THRESHOLD_DEV_POLYNOMIAL_V2 = - '0200000000000000d9c90e58fdeaa61c9aa35478b3c5476d545d7a719c16c82c91087978232a030033889cd0019e96a3d0850f7d18861a00e81f754052106aa7c3bdd6e535e3ad303b2620a4c316dc598e0f9c659b50ceb0932d4da8c161046fc44f25cf05dde900ebf6f83c5cb94288347ebf437e99fbb7a7eaf0c9873467352c1a9113f5fc0974d96cbf25462def50c39224da757ed300ce12e0fa8c6e73387cb43c69764bed41d0a0c55981642650b07fad1107a27b27fc8c552da3edd64494e8acc4de9a2600' - -export const PNP_THRESHOLD_DEV_PUBKEY_V3 = - '5o9Y516dvzZLy7E/SfOSm2kVh02t1rU1tkJrk55/HjhRSZtyHRgAOnbnvKJvQjAA1OE70LsYlrKK8PGNVOp7cVdrFbm9xbkew+BU6hdO473qierDOF4SjKQNToyh5UOB' -export const PNP_THRESHOLD_DEV_PK_SHARE_1_V3 = - '000000005b2c8089ead28a08233b6b16b2341542453523445950cfbd9bd2f1d09c8eee0c' -export const PNP_THRESHOLD_DEV_PK_SHARE_2_V3 = - '01000000f6c10aa979a0c33a3af5b03c37ffdf1d4a8517a5f9e6058e1d863337eeb59904' -export const PNP_THRESHOLD_DEV_PK_SHARE_3_V3 = - '02000000925795c808ee0d7752aff632bb40555350854362b8caf0bef5dea1379e42f00e' -export const PNP_THRESHOLD_DEV_POLYNOMIAL_V3 = - '0200000000000000e68f58e75e9dbf364bcbb13f49f3929b6915874dadd6b535b6426b939e7f1e3851499b721d18003a76e7bca26f423000d4e13bd0bb1896b28af0f18d54ea7b71576b15b9bdc5b91ec3e054ea174ee3bdea89eac3385e128ca40d4e8ca1e543813fae8439a057f8c17d4538afecf038624e552a8c226c9f82bfb7a072cff28fb7d26ab45801b67db270cec8037b8d7e016b1b78f7997160bd4ed1b54ab5d6be7663935992cd9c59ceb17010eccd708a9762df616c1fe45a220be634e21ba87581' - -export const PNP_THRESHOLD_DEV_POLYNOMIALS = [ - PNP_THRESHOLD_DEV_POLYNOMIAL_V1, - PNP_THRESHOLD_DEV_POLYNOMIAL_V2, - PNP_THRESHOLD_DEV_POLYNOMIAL_V3, -] - -export const PNP_THRESHOLD_DEV_PUBKEYS = [ - PNP_THRESHOLD_DEV_PUBKEY_V1, - PNP_THRESHOLD_DEV_PUBKEY_V2, - PNP_THRESHOLD_DEV_PUBKEY_V3, -] - -export const DOMAINS_THRESHOLD_DEV_PUBKEY_V1 = - 'zaetF6aXkBAkVwoUosuyQ8xiK2tKM9/zKrTPKbxoDoO7p6DSwbetk5uEICK+PjcAG4pGGY81jaUPSsPqlwIDfOy+RxJ2O+5ZPDM4I+b70MSYZYrsZ6qPxg+xtqLb9AOA' -export const DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V1 = - '010000000c63d9615b2ff0746562c0b438286544f029698a4205cd8b8f93afaa5b793211' -export const DOMAINS_THRESHOLD_DEV_PK_SHARE_2_V1 = - '020000001b63b2c531070b176f56042da68923c5859b9f82181559646b58445976de5f08' -export const DOMAINS_THRESHOLD_DEV_PK_SHARE_3_V1 = - '030000002b638b29085f37c3794a487512628c9f1cbd0dd70c72999d9dc205a2efa83812' -export const DOMAINS_THRESHOLD_DEV_POLYNOMIAL_V1 = - '0200000000000000cda7ad17a697901024570a14a2cbb243cc622b6b4a33dff32ab4cf29bc680e83bba7a0d2c1b7ad939b842022be3e37001b8a46198f358da50f4ac3ea9702037cecbe4712763bee593c333823e6fbd0c498658aec67aa8fc60fb1b6a2dbf403804e71a1bb1f51f2186f579048cb224f8993295e699ea14552506418df6fcf019ffe6f89253d6122cc97b8f8c5785674006c821ca2d596e4c0d75aba2b03e8ba082e002d24ebe5c48956ef96b8ac85f96c9c7929e8facac50b74b3aac792ad5d00' - -export const DOMAINS_THRESHOLD_DEV_PUBKEY_V2 = - 'rc9WQhFQn64w9FzlbVgyZi8Cd/bep+l3MtzPOWMInRQ3XoJMDSJ15SzBgE6M6JEAr58f9m2zZi6TMEcogbg3hHp37MUoybowzbGeed9jWqCWGQ0VBMFMaJLR8exNdtkA' -export const DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V2 = - '01000000a8070976747d9bb1fe56d822a57252ce3ddd5a8acef7e3ed94aeb52a16da4d04' -export const DOMAINS_THRESHOLD_DEV_PK_SHARE_2_V2 = - '020000003d8495ff96deb0109216d575bc0f6ff364466e830ecb4d0e860d869ef8b82e0b' -export const DOMAINS_THRESHOLD_DEV_PK_SHARE_3_V2 = - '03000000d2002289b93fc66f25d6d1c8d3ac8b188caf817c4e9eb72e776c5612db970f12' -export const DOMAINS_THRESHOLD_DEV_POLYNOMIAL_V2 = - '0200000000000000adcf564211509fae30f45ce56d5832662f0277f6dea7e97732dccf3963089d14375e824c0d2275e52cc1804e8ce89100af9f1ff66db3662e9330472881b837847a77ecc528c9ba30cdb19e79df635aa096190d1504c14c6892d1f1ec4d76d900c32eadf29b938d0466e566b527c798434931c6c2afd84fdd34aa5d620b15d19b6b1d59f9fa0c81150bf62d316a1b8f000708a46bd4c807cab0a60e9692e1efe74084ae1503172377e39600b8fd88b4885ee55adae7bb21993909da127d3c0c81' - -export const DOMAINS_THRESHOLD_DEV_PUBKEY_V3 = - 'OGHVPM0uXSduGBKQNyyGBr7IHXZQbnG9WopBhw5m0nddsmcoQP30/IBGCB0JGOsAemcw/mP43ueJxw7PPo/m+7JhFyu8cX7F61ULbmHAFd84wneZJf42U42rWSoC+IeB' -export const DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V3 = - '0100000030c5c6ae0959c96ccc3a31c73b1b603b9b448ccfc80dba2e496d31295caee40e' -export const DOMAINS_THRESHOLD_DEV_PK_SHARE_2_V3 = - '02000000768eb3c42a126abaa80eb8cae14006f2a98cdd175d40984ad84e881c7bf7da0d' -export const DOMAINS_THRESHOLD_DEV_PK_SHARE_3_V3 = - '03000000bc57a0da4bcb0a0885e23ece8766aca8b8d42e60f17276666730df0f9a40d10c' -export const DOMAINS_THRESHOLD_DEV_POLYNOMIAL_V3 = - '02000000000000003861d53ccd2e5d276e181290372c8606bec81d76506e71bd5a8a41870e66d2775db2672840fdf4fc8046081d0918eb007a6730fe63f8dee789c70ecf3e8fe6fbb261172bbc717ec5eb550b6e61c015df38c2779925fe36538dab592a02f887817f62a8f350751888132fca7dd7ff06731102483340145ff1571229884b06bfbfb25636e4bc6ad5dc294a09e45f7171012d042d5be90537c3f0eb70d51c7f7a6c09cee7af8c4af1750afde124a47a98330073af8c9011ab8a1571bc8ee958e200' - -export const DOMAINS_THRESHOLD_DEV_PUBKEY_V4 = - 'iRyLg54DDNq2c1TUAbsnc2VB5BwjBjBjJCysj6NO/Fmuki3LHjaSOscbNTQtZkIBTjBTALBDPzJAr1hDFebQTFHfg7oNaFUiEKC7P7Mhd0X9BJWNV8MEm+ZG4DymrAgA' -export const DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V4 = - '0100000044d1155eb821064919ef3b35625aa3595e2f0285d23181997836c6b18c661901' -export const DOMAINS_THRESHOLD_DEV_PK_SHARE_2_V4 = - '02000000b02c15e2769896f6c8b9bd2e3be1ddcac753683e2b991ac7c12531334122ee00' -export const DOMAINS_THRESHOLD_DEV_PK_SHARE_3_V4 = - '030000001c881466350f27a478843f281468183c3178cef78300b4f40a159cb4f5ddc200' -export const DOMAINS_THRESHOLD_DEV_POLYNOMIAL_V4 = - '0200000000000000891c8b839e030cdab67354d401bb27736541e41c23063063242cac8fa34efc59ae922dcb1e36923ac71b35342d6642014e305300b0433f3240af584315e6d04c51df83ba0d68552210a0bb3fb3217745fd04958d57c3049be646e03ca6ac08004a8cd44dd5f648a0f3cba05024829e25c79e603193fd7cdedce1cf400bf828bea0aee6b6b792c8efb6771713e6a30e01c8f8f981445a4455ee425a676133f8a095850245d32ce4765d83fc672a87c7116295c4b4927c51aec38b944260ea0200' - -export const DOMAINS_THRESHOLD_DEV_POLYNOMIALS = [ - DOMAINS_THRESHOLD_DEV_POLYNOMIAL_V1, - DOMAINS_THRESHOLD_DEV_POLYNOMIAL_V2, - DOMAINS_THRESHOLD_DEV_POLYNOMIAL_V3, - DOMAINS_THRESHOLD_DEV_POLYNOMIAL_V4, -] - -export const DOMAINS_THRESHOLD_DEV_PUBKEYS = [ - DOMAINS_THRESHOLD_DEV_PUBKEY_V1, - DOMAINS_THRESHOLD_DEV_PUBKEY_V2, - DOMAINS_THRESHOLD_DEV_PUBKEY_V3, - DOMAINS_THRESHOLD_DEV_PUBKEY_V4, -] diff --git a/packages/phone-number-privacy/common/src/utils/authentication.ts b/packages/phone-number-privacy/common/src/utils/authentication.ts deleted file mode 100644 index 7498d8d545..0000000000 --- a/packages/phone-number-privacy/common/src/utils/authentication.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { hexToBuffer, retryAsyncWithBackOffAndTimeout } from '@celo/base' -import { ContractKit } from '@celo/contractkit' -import { AccountsWrapper } from '@celo/contractkit/lib/wrappers/Accounts' -import { trimLeading0x } from '@celo/utils/lib/address' -import { verifySignature } from '@celo/utils/lib/signatureUtils' - -import Logger from 'bunyan' -import crypto from 'crypto' -import { Request } from 'express' -import { fetchEnv, rootLogger } from '..' -import { - AuthenticationMethod, - ErrorMessage, - ErrorType, - PhoneNumberPrivacyRequest, -} from '../interfaces' -import { FULL_NODE_TIMEOUT_IN_MS, RETRY_COUNT, RETRY_DELAY_IN_MS } from './constants' - -export type DataEncryptionKeyFetcher = (address: string) => Promise - -export function newContractKitFetcher( - contractKit: ContractKit, - logger: Logger, - fullNodeTimeoutMs: number = FULL_NODE_TIMEOUT_IN_MS, - fullNodeRetryCount: number = RETRY_COUNT, - fullNodeRetryDelayMs: number = RETRY_DELAY_IN_MS -): DataEncryptionKeyFetcher { - return (address: string) => - getDataEncryptionKey( - address, - contractKit, - logger, - fullNodeTimeoutMs, - fullNodeRetryCount, - fullNodeRetryDelayMs - ) -} - -/* - * Confirms that user is who they say they are and throws error on failure to confirm. - * Authorization header should contain the EC signed body - */ -export async function authenticateUser( - request: Request<{}, {}, R>, - logger: Logger, - fetchDEK: DataEncryptionKeyFetcher, - warnings: ErrorType[] = [] -): Promise { - logger.debug('Authenticating user') - - // https://tools.ietf.org/html/rfc7235#section-4.2 - const messageSignature = request.get('Authorization') - const message = JSON.stringify(request.body) - const signer = request.body.account - const authMethod = request.body.authenticationMethod - - if (!messageSignature || !signer) { - return false - } - - if (authMethod && authMethod === AuthenticationMethod.ENCRYPTION_KEY) { - let registeredEncryptionKey - try { - registeredEncryptionKey = await fetchDEK(signer) - } catch (err) { - // getDataEncryptionKey should only throw if there is a full-node connection issue. - // That is, it does not throw if the DEK is undefined or invalid - logger.error({ - err, - warning: ErrorMessage.FAILURE_TO_GET_DEK, - }) - warnings.push(ErrorMessage.FAILURE_TO_GET_DEK) - return false - } - if (!registeredEncryptionKey) { - logger.warn({ account: signer }, 'Account does not have registered encryption key') - return false - } else { - logger.info({ dek: registeredEncryptionKey, account: signer }, 'Found DEK for account') - if (verifyDEKSignature(message, messageSignature, registeredEncryptionKey, logger)) { - return true - } - } - } - - // Fallback to previous signing pattern - logger.info( - { account: signer, message, messageSignature }, - 'Message was not authenticated with DEK, attempting to authenticate using wallet key' - ) - // TODO This uses signature utils, why doesn't DEK authentication? - // (https://github.com/celo-org/celo-monorepo/issues/9803) - return verifySignature(message, messageSignature, signer) -} - -export function getMessageDigest(message: string) { - // NOTE: Elliptic will truncate the raw msg to 64 bytes before signing, - // so make sure to always pass the hex encoded msgDigest instead. - return crypto.createHash('sha256').update(JSON.stringify(message)).digest('hex') -} - -// Used primarily for signing requests with a DEK, counterpart of verifyDEKSignature -// For general signing, use SignatureUtils in @celo/utils -export function signWithRawKey(msg: string, rawKey: string) { - // NOTE: elliptic is disabled elsewhere in this library to prevent - // accidental signing of truncated messages. - // tslint:disable-next-line:import-blacklist - const EC = require('elliptic').ec - const ec = new EC('secp256k1') - - // Sign - const key = ec.keyFromPrivate(hexToBuffer(rawKey)) - return JSON.stringify(key.sign(getMessageDigest(msg)).toDER()) -} - -export function verifyDEKSignature( - message: string, - messageSignature: string, - registeredEncryptionKey: string, - logger?: Logger -) { - logger = logger ?? rootLogger(fetchEnv('SERVICE_NAME')) - try { - // NOTE: elliptic is disabled elsewhere in this library to prevent - // accidental signing of truncated messages. - // tslint:disable-next-line:import-blacklist - const EC = require('elliptic').ec - const ec = new EC('secp256k1') - const key = ec.keyFromPublic(trimLeading0x(registeredEncryptionKey), 'hex') - const parsedSig = JSON.parse(messageSignature) - // TODO why do we use a different signing method instead of SignatureUtils? - // (https://github.com/celo-org/celo-monorepo/issues/9803) - if (key.verify(getMessageDigest(message), parsedSig)) { - return true - } - return false - } catch (err) { - logger.error('Failed to verify signature with DEK') - logger.error({ err, dek: registeredEncryptionKey }) - return false - } -} - -export async function getDataEncryptionKey( - address: string, - contractKit: ContractKit, - logger: Logger, - fullNodeTimeoutMs: number, - fullNodeRetryCount: number, - fullNodeRetryDelayMs: number -): Promise { - try { - const res = await retryAsyncWithBackOffAndTimeout( - async () => { - const accountWrapper: AccountsWrapper = await contractKit.contracts.getAccounts() - return accountWrapper.getDataEncryptionKey(address) - }, - fullNodeRetryCount, - [], - fullNodeRetryDelayMs, - 1.5, - fullNodeTimeoutMs - ) - return res - } catch (error) { - logger.error('Failed to retrieve DEK: ' + error) - logger.error(ErrorMessage.FULL_NODE_ERROR) - throw error - } -} diff --git a/packages/phone-number-privacy/common/src/utils/config.utils.ts b/packages/phone-number-privacy/common/src/utils/config.utils.ts deleted file mode 100644 index 8b4abc33a6..0000000000 --- a/packages/phone-number-privacy/common/src/utils/config.utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -import BigNumber from 'bignumber.js' -import * as dotenv from 'dotenv' - -dotenv.config() - -export const toNum = (value: BigNumber.Value) => new BigNumber(value).toNumber() -export const toBool = (value: string | undefined, fallback: boolean) => - value ? value.toLowerCase() === 'true' : fallback - -export function fetchEnv(name: string): string { - if (process.env[name] === undefined || process.env[name] === '') { - throw new Error(`ENV var '${name}' was not defined`) - } - return process.env[name] as string -} - -export function fetchEnvOrDefault(name: string, defaultValue: string): string { - return process.env[name] === undefined || process.env[name] === '' - ? defaultValue - : (process.env[name] as string) -} diff --git a/packages/phone-number-privacy/common/src/utils/constants.ts b/packages/phone-number-privacy/common/src/utils/constants.ts deleted file mode 100644 index 1287c9086f..0000000000 --- a/packages/phone-number-privacy/common/src/utils/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const REASONABLE_BODY_CHAR_LIMIT: number = 16_000 -export const DB_TIMEOUT = 1000 -export const DB_POOL_MAX_SIZE = 50 -export const FULL_NODE_TIMEOUT_IN_MS = 1000 -export const RETRY_COUNT = 5 -export const RETRY_DELAY_IN_MS = 100 -export const KEY_VERSION_HEADER = 'odis-key-version' // headers must be all lower case diff --git a/packages/phone-number-privacy/common/src/utils/contracts.ts b/packages/phone-number-privacy/common/src/utils/contracts.ts deleted file mode 100644 index 8707f99f06..0000000000 --- a/packages/phone-number-privacy/common/src/utils/contracts.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ContractKit, HttpProviderOptions, newKit, newKitWithApiKey } from '@celo/contractkit' -import http from 'http' -import https from 'https' - -export interface BlockchainConfig { - provider: string - apiKey?: string -} - -export function getContractKit(config: BlockchainConfig): ContractKit { - return config.apiKey ? newKitWithApiKey(config.provider, config.apiKey) : newKit(config.provider) -} - -export function getContractKitWithAgent(config: BlockchainConfig): ContractKit { - const options: HttpProviderOptions = {} - options.agent = { - http: new http.Agent({ keepAlive: true }), - https: new https.Agent({ keepAlive: true }), - } - options.keepAlive = true - if (config.apiKey) { - options.headers = [] - options.headers.push({ - name: 'apiKey', - value: config.apiKey, - }) - } - return newKit(config.provider, undefined, options) -} diff --git a/packages/phone-number-privacy/common/src/utils/input-validation.ts b/packages/phone-number-privacy/common/src/utils/input-validation.ts deleted file mode 100644 index 514d6dd58f..0000000000 --- a/packages/phone-number-privacy/common/src/utils/input-validation.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { isValidAddress } from '@celo/utils/lib/address' -import isBase64 from 'is-base64' -import { PnpQuotaRequest, SignMessageRequest } from '../interfaces' -import { REASONABLE_BODY_CHAR_LIMIT } from './constants' - -export function hasValidAccountParam(requestBody: { account: string }): boolean { - return !!requestBody.account && isValidAddress(requestBody.account) -} - -export function isBodyReasonablySized(requestBody: SignMessageRequest | PnpQuotaRequest): boolean { - return JSON.stringify(requestBody).length <= REASONABLE_BODY_CHAR_LIMIT -} - -export function hasValidBlindedPhoneNumberParam(requestBody: SignMessageRequest): boolean { - return ( - !!requestBody.blindedQueryPhoneNumber && - requestBody.blindedQueryPhoneNumber.length === 64 && - isBase64(requestBody.blindedQueryPhoneNumber) - ) -} diff --git a/packages/phone-number-privacy/common/src/utils/key-version.ts b/packages/phone-number-privacy/common/src/utils/key-version.ts deleted file mode 100644 index de87277d4f..0000000000 --- a/packages/phone-number-privacy/common/src/utils/key-version.ts +++ /dev/null @@ -1,105 +0,0 @@ -import Logger from 'bunyan' -import { Request } from 'express' -import { Response as FetchResponse } from 'node-fetch' -import { ErrorMessage, KEY_VERSION_HEADER, OdisRequest, WarningMessage } from '..' - -export interface KeyVersionInfo { - keyVersion: number - threshold: number - polynomial: string - pubKey: string -} - -export function requestHasValidKeyVersion( - request: Request<{}, {}, OdisRequest>, - logger: Logger -): boolean { - try { - getRequestKeyVersion(request, logger) - return true - } catch (err) { - logger.debug('Error caught in requestHasValidKeyVersion') - logger.debug(err) - return false - } -} - -export function getRequestKeyVersion( - request: Request<{}, {}, OdisRequest>, - logger: Logger -): number | undefined { - const keyVersionHeader = request.headers[KEY_VERSION_HEADER] - const keyVersion = parseKeyVersionFromHeader(keyVersionHeader) - - if (keyVersion === undefined) { - return undefined - } - if (!isValidKeyVersion(keyVersion)) { - logger.error({ keyVersionHeader, keyVersion }, WarningMessage.INVALID_KEY_VERSION_REQUEST) - throw new Error(WarningMessage.INVALID_KEY_VERSION_REQUEST) - } - - logger.info({ keyVersion }, 'Request has valid key version') - return keyVersion -} - -export function responseHasExpectedKeyVersion( - response: FetchResponse, - expectedKeyVersion: number, - logger: Logger -): boolean { - let responseKeyVersion - try { - responseKeyVersion = getResponseKeyVersion(response, logger) - } catch (err) { - logger.debug('Error caught in responseHasExpectedKeyVersion') - logger.debug(err) - return false - } - - if (responseKeyVersion !== expectedKeyVersion) { - logger.error( - { expectedKeyVersion, responseKeyVersion }, - ErrorMessage.INVALID_KEY_VERSION_RESPONSE - ) - return false - } - - return true -} - -export function getResponseKeyVersion(response: FetchResponse, logger: Logger): number | undefined { - const keyVersionHeader = response.headers.get(KEY_VERSION_HEADER) - const keyVersion = parseKeyVersionFromHeader(keyVersionHeader) - - if (keyVersion === undefined) { - return undefined - } - if (!isValidKeyVersion(keyVersion)) { - logger.error({ keyVersionHeader, keyVersion }, ErrorMessage.INVALID_KEY_VERSION_RESPONSE) - throw new Error(ErrorMessage.INVALID_KEY_VERSION_RESPONSE) - } - - logger.info({ keyVersion }, 'Response has valid key version') - return keyVersion -} - -function parseKeyVersionFromHeader( - keyVersionHeader: string | string[] | undefined | null -): number | undefined { - if (keyVersionHeader === undefined || keyVersionHeader === null) { - return undefined - } - - const keyVersionHeaderString = keyVersionHeader.toString().trim() - - if (!keyVersionHeaderString.length || keyVersionHeaderString === 'undefined') { - return undefined - } - - return Number(keyVersionHeaderString) -} - -function isValidKeyVersion(keyVersion: number): boolean { - return Number.isInteger(keyVersion) && keyVersion >= 0 -} diff --git a/packages/phone-number-privacy/common/src/utils/logger.ts b/packages/phone-number-privacy/common/src/utils/logger.ts deleted file mode 100644 index 0114aebb64..0000000000 --- a/packages/phone-number-privacy/common/src/utils/logger.ts +++ /dev/null @@ -1,62 +0,0 @@ -import Logger, { createLogger, levelFromName, LogLevelString, stdSerializers } from 'bunyan' -import bunyanDebugStream from 'bunyan-debug-stream' -import { createStream } from 'bunyan-gke-stackdriver' -import { NextFunction, Request, Response } from 'express' -import { WarningMessage } from '../interfaces/errors' -import { fetchEnvOrDefault } from './config.utils' - -let _rootLogger: Logger | undefined - -export function rootLogger(serviceName: string): Logger { - if (_rootLogger !== undefined) { - return _rootLogger - } - - const logLevel = fetchEnvOrDefault('LOG_LEVEL', 'info') as LogLevelString - const logFormat = fetchEnvOrDefault('LOG_FORMAT', 'human') - - let stream: any - switch (logFormat) { - case 'stackdriver': - stream = createStream(levelFromName[logLevel]) - break - case 'json': - stream = { stream: process.stdout, level: logLevel } - break - default: - stream = { level: logLevel, stream: bunyanDebugStream() } - break - } - - _rootLogger = createLogger({ - name: serviceName ?? '', - serializers: stdSerializers, - streams: [stream], - }) - return _rootLogger -} - -export function loggerMiddleware( - serviceName: string -): (req: Request, res: Response, next?: NextFunction) => Logger { - return (req, res, next) => { - const requestLogger = rootLogger(serviceName).child({ - endpoint: req.path, - sessionID: req.body.sessionID, // May be undefined - }) - res.locals.logger = requestLogger - - if (!req.body.sessionID && req.path !== '/metrics' && req.path !== '/status') { - requestLogger.info(WarningMessage.MISSING_SESSION_ID) - } - - if (next) { - next() - } - return requestLogger - } -} - -export function genSessionID() { - return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) -} diff --git a/packages/phone-number-privacy/common/src/utils/responses.utils.ts b/packages/phone-number-privacy/common/src/utils/responses.utils.ts deleted file mode 100644 index fffe55a189..0000000000 --- a/packages/phone-number-privacy/common/src/utils/responses.utils.ts +++ /dev/null @@ -1,22 +0,0 @@ -import Logger from 'bunyan' -import { Response } from 'express' -import { OdisRequest, OdisResponse, WarningMessage } from '..' - -export function send< - I extends OdisRequest = OdisRequest, - O extends OdisResponse = OdisResponse ->(response: Response, body: O, status: number, logger: Logger) { - if (!response.headersSent) { - if (!body.success) { - if (body.error in WarningMessage) { - logger.warn({ error: body.error, status, body }, 'Responding with warning') - } else { - logger.error({ error: body.error, status, body }, 'Responding with error') - } - } else { - logger.info({ status, body }, 'Responding with success') - } - response.status(status).json(body) - logger.info('Completed send') - } -} diff --git a/packages/phone-number-privacy/common/test/domains.test.ts b/packages/phone-number-privacy/common/test/domains.test.ts deleted file mode 100644 index 814e0ced3d..0000000000 --- a/packages/phone-number-privacy/common/test/domains.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { - defined, - EIP712Object, - generateTypedDataHash, - noBool, - noNumber, - noString, -} from '@celo/utils/lib/sign-typed-data-utils' -import { bufferToHex } from '@ethereumjs/util' -import { DomainIdentifiers } from '../src/domains/constants' -import { Domain, domainEIP712, DomainOptions } from '../src/domains/domains' -import { SequentialDelayDomain } from '../src/domains/sequential-delay' - -// Compile-time check that Domain can be cast to type EIP712Object -export const TEST_DOMAIN_IS_EIP712: EIP712Object = {} as unknown as Domain - -// Compile-time check that DomainOptions can be cast to type EIP712Object -export const TEST_DOMAIN_OPTIONS_ARE_EIP712: EIP712Object = {} as unknown as DomainOptions - -describe('domainEIP712()', () => { - it('should generate the correct type data for SequentialDelayDomain instance', () => { - const domain: SequentialDelayDomain = { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [ - { delay: 0, resetTimer: noBool, batchSize: defined(2), repetitions: noNumber }, - { delay: 1, resetTimer: defined(false), batchSize: noNumber, repetitions: noNumber }, - { delay: 1, resetTimer: defined(true), batchSize: noNumber, repetitions: noNumber }, - { delay: 2, resetTimer: defined(false), batchSize: noNumber, repetitions: defined(1) }, - { delay: 4, resetTimer: noBool, batchSize: defined(2), repetitions: defined(2) }, - ], - address: defined('0x0000000000000000000000000000000000000b0b'), - salt: noString, - } - const expectedHash = '0x966edacc6cdf76b4536da958e82e360213b957508767a393ccf5c6b73db241d1' - const typedData = domainEIP712(domain) - // console.debug(JSON.stringify(typedData, null, 2)) - expect(bufferToHex(generateTypedDataHash(typedData))).toEqual(expectedHash) - }) -}) diff --git a/packages/phone-number-privacy/common/test/interfaces/requests.test.ts b/packages/phone-number-privacy/common/test/interfaces/requests.test.ts deleted file mode 100644 index 6feb23ae58..0000000000 --- a/packages/phone-number-privacy/common/test/interfaces/requests.test.ts +++ /dev/null @@ -1,306 +0,0 @@ -import { - defined, - EIP712Object, - generateTypedDataHash, - noBool, - noNumber, - noString, -} from '@celo/utils/lib/sign-typed-data-utils' -import { LocalWallet } from '@celo/wallet-local' -import { bufferToHex } from '@ethereumjs/util' -import { - Domain, - DomainIdentifiers, - SequentialDelayDomain, - SequentialDelayDomainSchema, -} from '../../src/domains' -import { - DisableDomainRequest, - disableDomainRequestEIP712, - disableDomainRequestSchema, - DomainQuotaStatusRequest, - domainQuotaStatusRequestEIP712, - domainQuotaStatusRequestSchema, - DomainRequestTypeTag, - DomainRestrictedSignatureRequest, - domainRestrictedSignatureRequestEIP712, - domainRestrictedSignatureRequestSchema, - verifyDisableDomainRequestAuthenticity, - verifyDomainQuotaStatusRequestAuthenticity, - verifyDomainRestrictedSignatureRequestAuthenticity, -} from '../../src/interfaces/requests' - -// Compile-time check that DomainRestrictedSignatureRequest can be cast to type EIP712Object. -export let TEST_DOMAIN_RESTRICTED_SIGNATURE_REQUEST_IS_EIP712: EIP712Object -TEST_DOMAIN_RESTRICTED_SIGNATURE_REQUEST_IS_EIP712 = - {} as unknown as DomainRestrictedSignatureRequest -TEST_DOMAIN_RESTRICTED_SIGNATURE_REQUEST_IS_EIP712 = - {} as unknown as DomainRestrictedSignatureRequest - -// Compile-time check that DomainQuotaStatusRequest can be cast to type EIP712Object. -export let TEST_DOMAIN_QUOTA_STATUS_REQUEST_IS_EIP712: EIP712Object -TEST_DOMAIN_QUOTA_STATUS_REQUEST_IS_EIP712 = {} as unknown as DomainQuotaStatusRequest -TEST_DOMAIN_QUOTA_STATUS_REQUEST_IS_EIP712 = {} as unknown as DomainQuotaStatusRequest - -// Compile-time check that DomainQuotaStatusRequest can be cast to type EIP712Object. -export let TEST_DISABLE_DOMAIN_REQUEST_IS_EIP712: EIP712Object -TEST_DISABLE_DOMAIN_REQUEST_IS_EIP712 = {} as unknown as DisableDomainRequest -TEST_DISABLE_DOMAIN_REQUEST_IS_EIP712 = {} as unknown as DisableDomainRequest - -describe('domainRestrictedSignatureRequestEIP712()', () => { - it('should generate the correct type data for request with SequentialDelayDomain', () => { - const request: DomainRestrictedSignatureRequest = { - type: DomainRequestTypeTag.SIGN, - domain: { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [{ delay: 0, resetTimer: noBool, batchSize: defined(2), repetitions: defined(10) }], - address: defined('0x0000000000000000000000000000000000000b0b'), - salt: noString, - }, - options: { - signature: defined(''), - nonce: defined(1), - }, - blindedMessage: '', - sessionID: noString, - } - const expectedHash = '0x9914e6bc3bd0d63727eeae4008654920b9879654f7159b1d5ab33768e61f56df' - const typedData = domainRestrictedSignatureRequestEIP712(request) - // console.debug(JSON.stringify(typedData, null, 2)) - expect(bufferToHex(generateTypedDataHash(typedData))).toEqual(expectedHash) - }) -}) - -describe('domainQuotaStatusRequestEIP712()', () => { - it('should generate the correct type data for request with SequentialDelayDomain', () => { - const request: DomainQuotaStatusRequest = { - type: DomainRequestTypeTag.QUOTA, - domain: { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [{ delay: 0, resetTimer: noBool, batchSize: defined(2), repetitions: defined(10) }], - address: defined('0x0000000000000000000000000000000000000b0b'), - salt: noString, - }, - options: { - signature: defined(''), - nonce: defined(2), - }, - sessionID: noString, - } - const expectedHash = '0x0c1545b83f28d8d0f24886fa0d21ac540af706dd6f9ee6d045bac17780a2656e' - const typedData = domainQuotaStatusRequestEIP712(request) - //console.debug(JSON.stringify(typedData, null, 2)) - expect(bufferToHex(generateTypedDataHash(typedData))).toEqual(expectedHash) - }) -}) - -describe('disableDomainRequestEIP712()', () => { - it('should generate the correct type data for request with SequentialDelayDomain', () => { - const request: DisableDomainRequest = { - type: DomainRequestTypeTag.DISABLE, - domain: { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [{ delay: 0, resetTimer: noBool, batchSize: defined(2), repetitions: defined(10) }], - address: defined('0x0000000000000000000000000000000000000b0b'), - salt: noString, - }, - options: { - signature: defined(''), - nonce: defined(2), - }, - sessionID: noString, - } - const expectedHash = '0xd30be7d1b1bb3a9a0b2b2148d9ea3fcae7775dc31ce984d658f90295887a323a' - const typedData = disableDomainRequestEIP712(request) - console.debug(JSON.stringify(typedData, null, 2)) - expect(bufferToHex(generateTypedDataHash(typedData))).toEqual(expectedHash) - }) -}) - -const wallet = new LocalWallet() -wallet.addAccount('0x00000000000000000000000000000000000000000000000000000000deadbeef') -wallet.addAccount('0x00000000000000000000000000000000000000000000000000000000bad516e9') -const walletAddress = wallet.getAccounts()[0]! -const badAddress = wallet.getAccounts()[1]! - -const authenticatedDomain: SequentialDelayDomain = { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [{ delay: 0, resetTimer: noBool, batchSize: defined(2), repetitions: defined(10) }], - address: defined(walletAddress), - salt: noString, -} - -const unauthenticatedDomain: SequentialDelayDomain = { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [{ delay: 0, resetTimer: noBool, batchSize: defined(2), repetitions: defined(10) }], - address: noString, - salt: noString, -} - -const manipulatedDomain: SequentialDelayDomain = { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [{ delay: 0, resetTimer: noBool, batchSize: defined(100), repetitions: defined(10) }], - address: defined(walletAddress), - salt: noString, -} - -const signatureRequest: DomainRestrictedSignatureRequest = { - type: DomainRequestTypeTag.SIGN, - domain: authenticatedDomain, - options: { - signature: noString, - nonce: defined(0), - }, - blindedMessage: '', - sessionID: noString, -} - -const quotaRequest: DomainQuotaStatusRequest = { - type: DomainRequestTypeTag.QUOTA, - domain: authenticatedDomain, - options: { - signature: noString, - nonce: defined(0), - }, - sessionID: noString, -} - -const disableRequest: DisableDomainRequest = { - type: DomainRequestTypeTag.DISABLE, - domain: authenticatedDomain, - options: { - signature: noString, - nonce: defined(0), - }, - sessionID: noString, -} - -const verifyCases = [ - { - request: signatureRequest, - typedDataBuilder: domainRestrictedSignatureRequestEIP712, - verifier: verifyDomainRestrictedSignatureRequestAuthenticity, - name: 'verifyDomainRestrictedSignatureRequestAuthenticity()', - }, - { - request: quotaRequest, - typedDataBuilder: domainQuotaStatusRequestEIP712, - verifier: verifyDomainQuotaStatusRequestAuthenticity, - name: 'verifyDomainQuotaStatusRequestAuthenticity()', - }, - { - request: disableRequest, - typedDataBuilder: disableDomainRequestEIP712, - verifier: verifyDisableDomainRequestAuthenticity, - name: 'verifyDisableDomainRequestAuthenticity()', - }, -] - -for (const { request, verifier, typedDataBuilder, name } of verifyCases) { - describe(name, () => { - it('should report a correctly signed request as verified', async () => { - //@ts-ignore type checking does not correctly infer types. - const typedData = typedDataBuilder(request) - const signature = await wallet.signTypedData(walletAddress, typedData) - const signed = { - ...request, - options: { - ...request.options, - signature: defined(signature), - }, - } - - //@ts-ignore type checking does not correctly infer types. - expect(verifier(signed)).toBe(true) - }) - - it('should report an unsigned message as unverified', async () => { - //@ts-ignore type checking does not correctly infer types. - expect(verifier(request)).toBe(false) - }) - - it('should report a manipulated message as unverified', async () => { - //@ts-ignore type checking does not correctly infer types. - const typedData = typedDataBuilder(request) - const signature = await wallet.signTypedData(walletAddress, typedData) - const signed = { - ...request, - options: { - ...request.options, - signature: defined(signature), - }, - } - //@ts-ignore type checking does not correctly infer types. - expect(verifier(signed)).toBe(true) - - const manipulated = { ...request, domain: manipulatedDomain } - //@ts-ignore type checking does not correctly infer types. - expect(verifier(manipulated)).toBe(false) - }) - - it('should report an incorrectly signed request as unverified', async () => { - //@ts-ignore type checking does not correctly infer types. - const typedData = typedDataBuilder(request) - const signature = await wallet.signTypedData(badAddress, typedData) - const signed = { - ...request, - options: { - ...request.options, - signature: defined(signature), - }, - } - - //@ts-ignore type checking does not correctly infer types. - expect(verifier(signed)).toBe(false) - }) - - it('should report requests against unauthenticated domains to be unverified', async () => { - const unauthenticatedRequest = { - ...request, - domain: unauthenticatedDomain, - options: { - signature: noString, - nonce: noNumber, - }, - } - //@ts-ignore type checking does not correctly infer types. - expect(verifier(unauthenticatedRequest)).toBe(false) - }) - }) -} - -const schemaCases = [ - { - request: signatureRequest, - schema: domainRestrictedSignatureRequestSchema(SequentialDelayDomainSchema), - name: 'verifyDomainRestrictedSignatureRequestSignature()', - }, - { - request: quotaRequest, - schema: domainQuotaStatusRequestSchema(SequentialDelayDomainSchema), - name: 'verifyDomainQuotaStatusRequestSignature()', - }, - { - request: disableRequest, - schema: disableDomainRequestSchema(SequentialDelayDomainSchema), - name: 'verifyDisableDomainRequestSignature()', - }, -] - -for (const { request, schema, name } of schemaCases) { - describe(name, () => { - it('should report a correctly constructed request as validated', async () => { - expect(schema.is(request)).toBe(true) - }) - - it('should report an invalid request as not validated', async () => { - expect(schema.is({ ...request, options: {} })).toBe(false) - }) - }) -} diff --git a/packages/phone-number-privacy/common/test/poprf.test.ts b/packages/phone-number-privacy/common/test/poprf.test.ts deleted file mode 100644 index 474d324c8f..0000000000 --- a/packages/phone-number-privacy/common/test/poprf.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import * as poprf from '@celo/poprf' -import { - PoprfClient, - PoprfCombiner, - PoprfServer, - ThresholdPoprfClient, - ThresholdPoprfServer, -} from '../src/poprf' - -const TEST_POPRF_KEYPAIR = poprf.keygen(Buffer.from('TEST POPRF KEYPAIR SEED')) -const TEST_THRESHOLD_N = 3 -const TEST_THRESHOLD_T = 2 -const TEST_THRESHOLD_POPRF_KEYS = poprf.thresholdKeygen( - TEST_THRESHOLD_N, - TEST_THRESHOLD_T, - Buffer.from('TEST THRESHOLD POPRF KEYPAIR SEED') -) - -const TEST_MESSAGE_A = Buffer.from('TEST MESSAGE A') -const TEST_MESSAGE_B = Buffer.from('TEST MESSAGE B') -const TEST_TAG_A = Buffer.from('TEST TAG A') -const TEST_TAG_B = Buffer.from('TEST TAG B') -const TEST_BLINDING_SEED = Buffer.from('TEST BLINDING SEED') - -describe('PoprfClient', () => { - it('results in a different blinding when called multiple times without a seed', () => { - const clientA = new PoprfClient(TEST_POPRF_KEYPAIR.publicKey, TEST_TAG_A, TEST_MESSAGE_A) - const clientB = new PoprfClient(TEST_POPRF_KEYPAIR.publicKey, TEST_TAG_A, TEST_MESSAGE_A) - expect(clientA.blindedMessage).not.toEqual(clientB.blindedMessage) - }) - - it('results in the same blinding when called multiple times with the same message and seed', () => { - const clientA = new PoprfClient( - TEST_POPRF_KEYPAIR.publicKey, - TEST_TAG_A, - TEST_MESSAGE_A, - TEST_BLINDING_SEED - ) - const clientB = new PoprfClient( - TEST_POPRF_KEYPAIR.publicKey, - TEST_TAG_A, - TEST_MESSAGE_A, - TEST_BLINDING_SEED - ) - expect(clientA.blindedMessage).toEqual(clientB.blindedMessage) - }) -}) - -describe('end-to-end', () => { - it('successfully completes client-server exchange', () => { - const server = new PoprfServer(TEST_POPRF_KEYPAIR.privateKey) - const client = new PoprfClient(TEST_POPRF_KEYPAIR.publicKey, TEST_TAG_A, TEST_MESSAGE_A) - - const response = server.blindEval(client.tag, client.blindedMessage) - const evaluation = client.unblindResponse(response) - - // POPRF hashed outputs should be 32 bytes. - expect(evaluation.length).toEqual(32) - expect(evaluation.toString('base64')).toEqual('Oh4FGO2zJ/jZDLkpW4LJk3xr5RdIHg0mYuGg2/b44+s=') - }) - - it('client rejects a exchange when the server uses the wrong key', () => { - const badPrivateKey = poprf.keygen(Buffer.from('BAD POPRF KEYPAIR SEED')).privateKey - const server = new PoprfServer(badPrivateKey) - const client = new PoprfClient(TEST_POPRF_KEYPAIR.publicKey, TEST_TAG_A, TEST_MESSAGE_A) - - const response = server.blindEval(client.tag, client.blindedMessage) - expect(() => client.unblindResponse(response)).toThrow(/verification failed/) - }) - - it('client rejects a exchange when the server uses the wrong tag', () => { - const server = new PoprfServer(TEST_POPRF_KEYPAIR.privateKey) - const client = new PoprfClient(TEST_POPRF_KEYPAIR.publicKey, TEST_TAG_A, TEST_MESSAGE_A) - - const response = server.blindEval(TEST_TAG_B, client.blindedMessage) - expect(() => client.unblindResponse(response)).toThrow(/verification failed/) - }) - - it('client rejects a exchange when the server uses the wrong message', () => { - const server = new PoprfServer(TEST_POPRF_KEYPAIR.privateKey) - const client = new PoprfClient(TEST_POPRF_KEYPAIR.publicKey, TEST_TAG_A, TEST_MESSAGE_A) - - const badMessage = new PoprfClient(TEST_POPRF_KEYPAIR.publicKey, TEST_TAG_A, TEST_MESSAGE_B) - .blindedMessage - - const response = server.blindEval(TEST_TAG_A, badMessage) - expect(() => client.unblindResponse(response)).toThrow(/verification failed/) - }) - - it('successfully completes client-server exchange with combiner', () => { - const servers = [...Array(TEST_THRESHOLD_N).keys()].map( - (i) => new ThresholdPoprfServer(TEST_THRESHOLD_POPRF_KEYS.getShare(i)) - ) - const combiner = new PoprfCombiner(TEST_THRESHOLD_T) - const client = new PoprfClient( - TEST_THRESHOLD_POPRF_KEYS.thresholdPublicKey, - TEST_TAG_A, - TEST_MESSAGE_A - ) - - const blindedPartials = servers.map((s) => - s.blindPartialEval(client.tag, client.blindedMessage) - ) - const response = combiner.blindAggregate(blindedPartials) - expect(response).toBeDefined() - if (response === undefined) { - throw new Error('response is undefined') - } - const evaluation = client.unblindResponse(response) - - // POPRF hashed outputs should be 32 bytes. - expect(evaluation.length).toEqual(32) - expect(evaluation.toString('base64')).toEqual('C1jKGStMWC3lNpYDV61D+3waetY0bHlD4ElYzV+Isqc=') - }) - - it('successfully completes client-server exchange with threshold client and server', () => { - const servers = [...Array(TEST_THRESHOLD_N).keys()].map( - (i) => new ThresholdPoprfServer(TEST_THRESHOLD_POPRF_KEYS.getShare(i)) - ) - const client = new ThresholdPoprfClient( - TEST_THRESHOLD_POPRF_KEYS.thresholdPublicKey, - TEST_THRESHOLD_POPRF_KEYS.polynomial, - TEST_TAG_A, - TEST_MESSAGE_A - ) - - const blindedPartials = servers.map((s) => - s.blindPartialEval(client.tag, client.blindedMessage) - ) - const partials = blindedPartials.map((r) => client.unblindPartialResponse(r)) - expect(partials.map((p) => p.toString('base64'))).toEqual([ - 'AQAAALCjiT0dQp/OCz5OOXNmAwOV6waen+w9hgZsefHbCXPENhQv1kPK/aSSRbUqWHGDAFaN2IDxT+bnP6bfVjEvzMs1zZtLJZXcGTauucpGx1ESGc8TqKutLIKnGg4iKp0qAH6J7os1L11ARrqv3gSMt0KBD6dd8pK8/4bkx219wkLFu3SRM0/DYAUrgNkDnTGRAH88fDHRcLwesFo65hux4Owq92C6wqJTnsLGxYOoEm7tM8Eycx2M/eHS1QhyNC5NAZFrtacLSp3GHA46CD5oxsZ9zeAWjv0pCJA8tp2gLdHyjJW0czaTDY8EEn5evr9UAB0+sC5ELC0ljNchw0otkWuOEUvV3V3ygQId6/r/s7gMDeCw3MacTT2HRyB2W41oAG6h7rlnIIEdtIQR/P3JAUcf7dTv5HN65LgkKk+4jh0azqsQFYgKUL776F9dsz+pAeFFyND44saFuJf4uK3KeTspY5EWOPUK2o9oU86z0OPvBlkJl6o6GUz2OzFr4AmYAN0IYZy9cd6SGrEUpLIBFe2iFxGRuzMK100USPDrM8MkawNTsOla1j9OdgBilhZsAUJP00Rhrx8zfx0olc2Hk29ZK3MhdJ++x94W+z1KMMDu+/5us6uCzUU6rngt1mDgAC4Y2bv8xwKYePddddCQ2NSnzKTHt39ju9T73hr575IlXibX9/kNwILmrpf4iwhQANMbwK4IG9dMG7iLNjfy4efOENAkRF3gQ5+WWg3gRKsLo74672nJPmmKsEpPW4Y2AQ==', - 'AgAAAA94+5GfNTTztPtB8asRkd+U9ycdzhllEPPyeuwhGpFyXUruFEs4wsbLCI6sQHntAEYs52/06QnyTDSNAFhjaaZio4+OgmxWhkdcqc9HmjRSWWnGSfgEqAMPYgCBh7sJAezrKTlMkqkzPCoq0HND9CYpyMQ/6o+r4wteRapcDhDCltkRLu9KxPkBMqf5/VvNAL6XRUFAWNpi4MUKNnp8D3vIiVS5tlFuSa5BX3VzJO9CXLR16bXIgLkR55Lwz5qkAXkhKsGPWGinmKVRQw4s5CySg6ERS0RN/Q54dBXVhvXNkEyZ+e1aaO1sonUsSYteAMR6TmdGpxEuXQirqKRuoj5p8DxWI5qA3Jq+QhbdIGP091gK9oJYG3oIUsPKuGAvAfdZOA/a3S7woEWhXj6FkBTiIqP/CJvTO7wd7rMDvSlrn7Qg4sfnqtEVhklDgPHpAH9xiw7OYO9n6V54+KMK9BriSqkmek/PR5BcWJmwdK1M6kDtQ49z5DM/ZZoM0GnIAKt7Id8WL+Q0OTjk1U4Y4eGf+wtlSU1V3SqVYibXfjs3bCwfm2osZ5q1O1yLcxZkAdnxKoQuF09wmPKQIUwV6D6+AJLAQTbAwkuYjOH7o5zsDvYLf6q08dfJQtT5Vxd2AQMwsfFBOVtCp+xoMkSrqknFIJsJEfeqLqup0C86G3Uq7RR70ou9MBe0fMgK1H4lAM6/Ve7sfjYxbwwndWMBhhzrb6QS1KY1LFzP12aBHyL3BkyluQhzoKEZxsfRB34fAA==', - 'AwAAAHxYwuz82TqEoRNqAA//f2NWDBBfN44NNP+Uxemrxi9Soe2EfRg7hwJXKeD4UU/OABySagQyLgF7Oy2iAzSxITB2ja0BUycKZVcQwu641PodT6D448t6M78Z5ISP2t+fASemh30SvQuIcdCGag2Wn8C8mb5TOJBOxSO2ybxtpp8RbBxvL+YASTMy3uF/y6ysAcfISp9zYDwo9nB2eeXOM+fwlTznGL1h+VWbZ497Uw0nR6o1UR53gZlraVVy+U/+AKW5R5PIr+2daV/B0WidOyJpIsOqj4tZ3ul2uDjEP1aDn5y2aNflmsPxQ6NN1BwzAQ1CyRH6iZzVp3/huJNoUJQknWCnaIaF34CpP6ajdKx6ts/Z+SBLaN25PFyK/KlIAKzQQkNPhJ+kLTODpDhbjtbsCDVknPYizswljLuFKp7mTW0pQhJ+GMEVbqdV+j/PAAiPCnBVC+1Up9cK0FSxJq6X9/vUNd10TsZn1tVIcVzj3lE1HNqPHnHnHqLR+pKMAPOKzFQfG8j05WGZDJjWfHxWOr5XL8zwg3Y0O4+WVcOT9AfL8Tu5djvZop/JCq42ARfnz/dI8VWucQ41A39oW06UJYnahWSxKWShhXnWe9/CQHqJb6nlRLJkkmLXbDP9ALZs/xmpVmL+UuIHCrIEqtL5JYdBOrclIxl+IT8MOHnQT58HB+D4i5ATfZ1qhCOgALgOKPPHxuFqmr6fUloSXNqZoEo/RzQqSC1IpubDIM2je2hJu5zPAPP8+OKn/ndrAQ==', - ]) - - const combiner = new PoprfCombiner(TEST_THRESHOLD_T) - const evaluation = combiner.aggregate(partials) - expect(evaluation).toBeDefined() - if (evaluation === undefined) { - throw new Error('evaluation is undefined') - } - - // POPRF hashed outputs should be 32 bytes. - expect(evaluation.length).toEqual(32) - expect(evaluation.toString('base64')).toEqual('C1jKGStMWC3lNpYDV61D+3waetY0bHlD4ElYzV+Isqc=') - }) -}) diff --git a/packages/phone-number-privacy/common/test/utils/authentication.test.ts b/packages/phone-number-privacy/common/test/utils/authentication.test.ts deleted file mode 100644 index 355eb630de..0000000000 --- a/packages/phone-number-privacy/common/test/utils/authentication.test.ts +++ /dev/null @@ -1,333 +0,0 @@ -import { hexToBuffer } from '@celo/base' -import { ContractKit } from '@celo/contractkit' -import Logger from 'bunyan' -import { Request } from 'express' -import { ErrorMessage, ErrorType } from '../../lib' -import { AuthenticationMethod } from '../../src/interfaces/requests' -import * as auth from '../../src/utils/authentication' -import { newContractKitFetcher } from '../../src/utils/authentication' - -describe('Authentication test suite', () => { - const logger = Logger.createLogger({ - name: 'logger', - level: 'warn', - }) - - describe('authenticateUser utility', () => { - it("Should fail authentication with missing 'Authorization' header", async () => { - const sampleRequest: Request = { - get: (_: string) => '', - body: { - account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', - }, - } as Request - const dekFetcher = newContractKitFetcher({} as ContractKit, logger) - const warnings: ErrorType[] = [] - - const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) - - expect(success).toBe(false) - expect(warnings).toEqual([]) - }) - - it('Should fail authentication with missing signer', async () => { - const sampleRequest: Request = { - get: (name: string) => (name === 'Authorization' ? 'Test' : ''), - body: {}, - } as Request - const dekFetcher = newContractKitFetcher({} as ContractKit, logger) - - const warnings: ErrorType[] = [] - - const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) - - expect(success).toBe(false) - expect(warnings).toEqual([]) - }) - - it('Should fail authentication with error in getDataEncryptionKey', async () => { - const sampleRequest: Request = { - get: (name: string) => (name === 'Authorization' ? 'Test' : ''), - body: { - account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - }, - } as Request - const dekFetcher = newContractKitFetcher({} as ContractKit, logger) - - const warnings: ErrorType[] = [] - - const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) - - expect(success).toBe(false) - expect(warnings).toEqual([ErrorMessage.FAILURE_TO_GET_DEK]) - }) - - it('Should fail authentication when key is not registered', async () => { - const sampleRequest: Request = { - get: (name: string) => (name === 'Authorization' ? 'Test' : ''), - body: { - account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - }, - } as Request - const mockContractKit = { - contracts: { - getAccounts: async () => { - return Promise.resolve({ - getDataEncryptionKey: async (_: string) => { - return '' - }, - }) - }, - }, - } as ContractKit - const dekFetcher = newContractKitFetcher(mockContractKit, logger) - - const warnings: ErrorType[] = [] - - const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) - - expect(success).toBe(false) - expect(warnings).toEqual([]) - }) - - it('Should fail authentication when key is registered but not valid', async () => { - const sampleRequest: Request = { - get: (name: string) => (name === 'Authorization' ? 'Test' : ''), - body: { - account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - }, - } as Request - const mockContractKit = { - contracts: { - getAccounts: async () => { - return Promise.resolve({ - getDataEncryptionKey: async (_: string) => { - return 'notAValidKeyEncryption' - }, - }) - }, - }, - } as ContractKit - const dekFetcher = newContractKitFetcher(mockContractKit, logger) - - const warnings: ErrorType[] = [] - - const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher) - - expect(success).toBe(false) - expect(warnings).toEqual([]) - }) - - it('Should succeed authentication when key is registered and valid', async () => { - const rawKey = '41e8e8593108eeedcbded883b8af34d2f028710355c57f4c10a056b72486aa04' - const body = { - account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - } - const sig = auth.signWithRawKey(JSON.stringify(body), rawKey) - const sampleRequest: Request = { - get: (name: string) => (name === 'Authorization' ? sig : ''), - body, - } as Request - const mockContractKit = { - contracts: { - getAccounts: async () => { - return Promise.resolve({ - getDataEncryptionKey: async (_: string) => { - // NOTE: elliptic is disabled elsewhere in this library to prevent - // accidental signing of truncated messages. - // tslint:disable-next-line:import-blacklist - const EC = require('elliptic').ec - const ec = new EC('secp256k1') - const key = ec.keyFromPrivate(hexToBuffer(rawKey)) - return key.getPublic(true, 'hex') - }, - }) - }, - }, - } as ContractKit - - const warnings: ErrorType[] = [] - const dekFetcher = newContractKitFetcher(mockContractKit, logger) - - const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) - - expect(success).toBe(true) - expect(warnings).toEqual([]) - }) - - it('Should fail authentication when the message is manipulated', async () => { - const rawKey = '41e8e8593108eeedcbded883b8af34d2f028710355c57f4c10a056b72486aa04' - const body = { - account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - } - const message = JSON.stringify(body) - - // Modify every fourth character and check that the signature becomes invalid. - for (let i = 0; i < message.length; i += 4) { - const modified = - message.slice(0, i) + - String.fromCharCode(message.charCodeAt(i) + 1) + - message.slice(i + 1) - const sig = auth.signWithRawKey(modified, rawKey) - const sampleRequest: Request = { - get: (name: string) => (name === 'Authorization' ? sig : ''), - body, - } as Request - const mockContractKit = { - contracts: { - getAccounts: async () => { - return Promise.resolve({ - getDataEncryptionKey: async (_: string) => { - // NOTE: elliptic is disabled elsewhere in this library to prevent - // accidental signing of truncated messages. - // tslint:disable-next-line:import-blacklist - const EC = require('elliptic').ec - const ec = new EC('secp256k1') - const key = ec.keyFromPrivate(hexToBuffer(rawKey)) - return key.getPublic(true, 'hex') - }, - }) - }, - }, - } as ContractKit - - const warnings: ErrorType[] = [] - - const dekFetcher = newContractKitFetcher(mockContractKit, logger) - - const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) - - expect(success).toBe(false) - expect(warnings).toEqual([]) - } - }) - - it('Should fail authentication when the key is incorrect', async () => { - const rawKey = '41e8e8593108eeedcbded883b8af34d2f028710355c57f4c10a056b72486aa04' - const body = { - account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - } - const sig = auth.signWithRawKey(JSON.stringify(body), rawKey) - const sampleRequest: Request = { - get: (name: string) => (name === 'Authorization' ? sig : ''), - body, - } as Request - - const mockContractKit = { - contracts: { - getAccounts: async () => { - return Promise.resolve({ - getDataEncryptionKey: async (_: string) => { - // NOTE: elliptic is disabled elsewhere in this library to prevent - // accidental signing of truncated messages. - // tslint:disable-next-line:import-blacklist - const EC = require('elliptic').ec - const ec = new EC('secp256k1') - // Send back a manipulated key. - const key = ec.keyFromPrivate(hexToBuffer('a' + rawKey.slice(1))) - return key.getPublic(true, 'hex') - }, - }) - }, - }, - } as ContractKit - - const warnings: ErrorType[] = [] - - const dekFetcher = newContractKitFetcher(mockContractKit, logger) - - const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) - - expect(success).toBe(false) - expect(warnings).toEqual([]) - }) - - it('Should fail authentication when the sigature is modified', async () => { - const rawKey = '41e8e8593108eeedcbded883b8af34d2f028710355c57f4c10a056b72486aa04' - const body = { - account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - } - // Manipulate the signature. - const sig = auth.signWithRawKey(JSON.stringify(body), rawKey) - const modified = JSON.stringify([0] + JSON.parse(sig)) - const sampleRequest: Request = { - get: (name: string) => (name === 'Authorization' ? modified : ''), - body, - } as Request - - const mockContractKit = { - contracts: { - getAccounts: async () => { - return Promise.resolve({ - getDataEncryptionKey: async (_: string) => { - // NOTE: elliptic is disabled elsewhere in this library to prevent - // accidental signing of truncated messages. - // tslint:disable-next-line:import-blacklist - const EC = require('elliptic').ec - const ec = new EC('secp256k1') - // Send back a manipulated key. - const key = ec.keyFromPrivate(hexToBuffer(rawKey)) - return key.getPublic(true, 'hex') - }, - }) - }, - }, - } as ContractKit - - const warnings: ErrorType[] = [] - - const dekFetcher = newContractKitFetcher(mockContractKit, logger) - - const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) - - expect(success).toBe(false) - expect(warnings).toEqual([]) - }) - - it('Should fail authentication when key is registered and valid and signature is incorrectly generated', async () => { - const rawKey = '41e8e8593108eeedcbded883b8af34d2f028710355c57f4c10a056b72486aa04' - const body = { - account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - } - // NOTE: elliptic is disabled elsewhere in this library to prevent - // accidental signing of truncated messages. - // tslint:disable-next-line:import-blacklist - const EC = require('elliptic').ec - const ec = new EC('secp256k1') - const key = ec.keyFromPrivate(hexToBuffer(rawKey)) - const sig = JSON.stringify(key.sign(JSON.stringify(body)).toDER()) - - const sampleRequest: Request = { - get: (name: string) => (name === 'Authorization' ? sig : ''), - body, - } as Request - const mockContractKit = { - contracts: { - getAccounts: async () => { - return Promise.resolve({ - getDataEncryptionKey: async (_: string) => { - return key.getPublic(true, 'hex') - }, - }) - }, - }, - } as ContractKit - const dekFetcher = newContractKitFetcher(mockContractKit, logger) - - const warnings: ErrorType[] = [] - - const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) - - expect(success).toBe(false) - expect(warnings).toEqual([]) - }) - }) -}) diff --git a/packages/phone-number-privacy/common/test/utils/input-validation.test.ts b/packages/phone-number-privacy/common/test/utils/input-validation.test.ts deleted file mode 100644 index f697dfe7db..0000000000 --- a/packages/phone-number-privacy/common/test/utils/input-validation.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as utils from '../../src/utils/input-validation' - -describe('Input Validation test suite', () => { - describe('hasValidAccountParam utility', () => { - it('Should return true for proper address', () => { - const sampleData = { - account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', - } - - const result = utils.hasValidAccountParam(sampleData) - - expect(result).toBeTruthy() - }) - - it('Should return false for nonsense address', () => { - const sampleData = { - account: '0xAA', - } - - const result = utils.hasValidAccountParam(sampleData) - - expect(result).toBeFalsy() - }) - - it('Should return false with missing address', () => { - const sampleData = { - account: '', - } - - const result = utils.hasValidAccountParam(sampleData) - - expect(result).toBeFalsy() - }) - }) -}) diff --git a/packages/phone-number-privacy/common/test/utils/key-version.test.ts b/packages/phone-number-privacy/common/test/utils/key-version.test.ts deleted file mode 100644 index 46db29d71e..0000000000 --- a/packages/phone-number-privacy/common/test/utils/key-version.test.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { Request } from 'express' -import { Response as FetchResponse } from 'node-fetch' -import { - ErrorMessage, - getRequestKeyVersion, - getResponseKeyVersion, - KEY_VERSION_HEADER, - requestHasValidKeyVersion, - responseHasExpectedKeyVersion, - rootLogger, - WarningMessage, -} from '../../src' - -describe('key version test suite', () => { - const logger = rootLogger('key version test suite') - - const request = { - headers: {}, - } as Request - - let response: FetchResponse - - const invalidKeyVersionHeaders: (string | string[])[] = [ - 'a', - '-1', - '1.5', - '1a', - 'blah', - 'one', - '-', - '+', - ['1', '2', '3'], - ' . ', - ] - - beforeEach(() => { - delete request.headers[KEY_VERSION_HEADER] - response = new FetchResponse() - }) - - describe(getRequestKeyVersion, () => { - it(`Should return undefined if key version header has not been set`, () => { - const res = getRequestKeyVersion(request, logger) - expect(res).toBe(undefined) - }) - - it(`Should return undefined if key version header is undefined`, () => { - request.headers[KEY_VERSION_HEADER] = undefined - const res = getRequestKeyVersion(request, logger) - expect(res).toBe(undefined) - }) - - it(`Should return undefined if key version header is empty`, () => { - request.headers[KEY_VERSION_HEADER] = '' - const res = getRequestKeyVersion(request, logger) - expect(res).toBe(undefined) - }) - - it(`Should return undefined if key version header is whitespace`, () => { - request.headers[KEY_VERSION_HEADER] = ' ' - const res = getRequestKeyVersion(request, logger) - expect(res).toBe(undefined) - }) - - for (let kv = 0; kv <= 10; kv++) { - it(`Should return valid key version header ${kv}`, () => { - request.headers[KEY_VERSION_HEADER] = kv.toString() - const res = getRequestKeyVersion(request, logger) - expect(res).toBe(kv) - }) - } - - it(`Should return valid key version header when there's whitespace`, () => { - request.headers[KEY_VERSION_HEADER] = ' 1 ' - const res = getRequestKeyVersion(request, logger) - expect(res).toBe(1) - }) - - invalidKeyVersionHeaders.forEach((kv) => { - it(`Should throw for invalid key version ${kv}`, () => { - request.headers[KEY_VERSION_HEADER] = kv.toString() - expect(() => getRequestKeyVersion(request, logger)).toThrow( - WarningMessage.INVALID_KEY_VERSION_REQUEST - ) - }) - }) - }) - - describe(requestHasValidKeyVersion, () => { - it(`Should return true if key version header has not been set`, () => { - const res = requestHasValidKeyVersion(request, logger) - expect(res).toBe(true) - }) - it(`Should return true if key version header is undefined`, () => { - request.headers[KEY_VERSION_HEADER] = undefined - const res = requestHasValidKeyVersion(request, logger) - expect(res).toBe(true) - }) - it(`Should return true if key version header is empty`, () => { - request.headers[KEY_VERSION_HEADER] = '' - const res = requestHasValidKeyVersion(request, logger) - expect(res).toBe(true) - }) - - it(`Should return true if key version header is whitespace`, () => { - request.headers[KEY_VERSION_HEADER] = ' ' - const res = requestHasValidKeyVersion(request, logger) - expect(res).toBe(true) - }) - - for (let kv = 0; kv <= 10; kv++) { - it(`Should return true for valid key version header ${kv}`, () => { - request.headers[KEY_VERSION_HEADER] = kv.toString() - const res = requestHasValidKeyVersion(request, logger) - expect(res).toBe(true) - }) - } - - it(`Should return true for valid key version header when there's whitespace`, () => { - request.headers[KEY_VERSION_HEADER] = ' 1 ' - const res = requestHasValidKeyVersion(request, logger) - expect(res).toBe(true) - }) - - invalidKeyVersionHeaders.forEach((kv) => { - it(`Should return false for invalid key version ${kv}`, () => { - request.headers[KEY_VERSION_HEADER] = kv.toString() - const res = requestHasValidKeyVersion(request, logger) - expect(res).toBe(false) - }) - }) - }) - - describe(getResponseKeyVersion, () => { - it(`Should return undefined if key version header has not been set`, () => { - const res = getResponseKeyVersion(response, logger) - expect(res).toBe(undefined) - }) - it(`Should return undefined if key version header is undefined`, () => { - response.headers.delete(KEY_VERSION_HEADER) - const res = getResponseKeyVersion(response, logger) - expect(res).toBe(undefined) - }) - - it(`Should return undefined if key version header is empty`, () => { - response.headers.set(KEY_VERSION_HEADER, '') - const res = getResponseKeyVersion(response, logger) - expect(res).toBe(undefined) - }) - - it(`Should return undefined if key version header is whitespace`, () => { - response.headers.set(KEY_VERSION_HEADER, ' ') - const res = getResponseKeyVersion(response, logger) - expect(res).toBe(undefined) - }) - - for (let kv = 0; kv <= 10; kv++) { - it(`Should return valid key version header ${kv}`, () => { - response.headers.set(KEY_VERSION_HEADER, kv.toString()) - const res = getResponseKeyVersion(response, logger) - expect(res).toBe(kv) - }) - } - - it(`Should return valid key version header when there's whitespace`, () => { - response.headers.set(KEY_VERSION_HEADER, ' 1 ') - const res = getResponseKeyVersion(response, logger) - expect(res).toBe(1) - }) - - invalidKeyVersionHeaders.forEach((kv) => { - it(`Should throw for invalid key version ${kv}`, () => { - response.headers.set(KEY_VERSION_HEADER, kv.toString()) - expect(() => getResponseKeyVersion(response, logger)).toThrow( - ErrorMessage.INVALID_KEY_VERSION_RESPONSE - ) - }) - }) - }) - - describe(responseHasExpectedKeyVersion, () => { - const testCases = [ - { - responseKeyVersion: 1, - expectedKeyVersion: 1, - expectedResult: true, - }, - { - responseKeyVersion: 2, - expectedKeyVersion: 1, - expectedResult: false, - }, - { - responseKeyVersion: undefined, - expectedKeyVersion: 1, - expectedResult: false, - }, - { - responseKeyVersion: -1, - expectedKeyVersion: -1, - expectedResult: false, - }, - { - responseKeyVersion: 1.5, - expectedKeyVersion: 1.5, - expectedResult: false, - }, - { - responseKeyVersion: 'a', - expectedKeyVersion: Number('a'), - expectedResult: false, - }, - ] - - testCases.forEach((testCase) => { - it(JSON.stringify(testCase), () => { - const { responseKeyVersion, expectedKeyVersion, expectedResult } = testCase - if (responseKeyVersion === undefined) { - response.headers.delete(KEY_VERSION_HEADER) - } else { - response.headers.set(KEY_VERSION_HEADER, responseKeyVersion.toString()) - } - const res = responseHasExpectedKeyVersion(response, expectedKeyVersion, logger) - expect(res).toBe(expectedResult) - }) - }) - }) -}) diff --git a/packages/phone-number-privacy/common/test/utils/sequential-delay.test.ts b/packages/phone-number-privacy/common/test/utils/sequential-delay.test.ts deleted file mode 100644 index decd72f9de..0000000000 --- a/packages/phone-number-privacy/common/test/utils/sequential-delay.test.ts +++ /dev/null @@ -1,327 +0,0 @@ -import { defined, noBool, noNumber, noString } from '@celo/utils/lib/sign-typed-data-utils' -import { - checkSequentialDelayRateLimit, - DomainIdentifiers, - SequentialDelayDomain, - SequentialDelayResult, -} from '../../src/domains' - -type TestAttempt = { - timestamp: number - expectedResult: SequentialDelayResult -} - -describe('Sequential Delay Test Suite', () => { - const checkTestAttempts = (t: number, domain: SequentialDelayDomain, attempts: TestAttempt[]) => { - let result: SequentialDelayResult | undefined - for (const attempt of attempts) { - console.log(result) - console.log(`t + ${attempt.timestamp - t}`) - result = checkSequentialDelayRateLimit(domain, attempt.timestamp, result?.state) - expect(result).toEqual(attempt.expectedResult) - } - } - - describe('checkSequentialDelayRateLimit', () => { - it('should not accept attempts until initial delay', () => { - const t = 0 // initial delay - - const domain: SequentialDelayDomain = { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [{ delay: t, resetTimer: noBool, batchSize: noNumber, repetitions: noNumber }], - address: noString, - salt: noString, - } - - const attempts: TestAttempt[] = [ - { - timestamp: t - 1, - expectedResult: { - accepted: false, - notBefore: 0, - state: { timer: 0, counter: 0, disabled: false, now: t - 1 }, - }, - }, - { - timestamp: t, - expectedResult: { - accepted: true, - state: { timer: t, counter: 1, disabled: false, now: t }, - }, - }, - ] - - checkTestAttempts(t, domain, attempts) - }) - - it('should accept multiple requests when batchSize is greater than one', () => { - const t = 0 // initial delay - - const domain: SequentialDelayDomain = { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [{ delay: t, batchSize: defined(2), resetTimer: noBool, repetitions: noNumber }], - address: noString, - salt: noString, - } - - const attempts: TestAttempt[] = [ - { - timestamp: t + 1, - expectedResult: { - accepted: true, - state: { timer: t + 1, counter: 1, disabled: false, now: t + 1 }, - }, - }, - { - timestamp: t + 1, - expectedResult: { - accepted: true, - state: { timer: t + 1, counter: 2, disabled: false, now: t + 1 }, - }, - }, - { - timestamp: t + 1, - expectedResult: { - accepted: false, - notBefore: undefined, - state: { timer: t + 1, counter: 2, disabled: false, now: t + 1 }, - }, - }, - ] - - checkTestAttempts(t, domain, attempts) - }) - - it('should reject requests when disabled is true', () => { - const t = 0 // initial delay - - const domain: SequentialDelayDomain = { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [{ delay: t, batchSize: defined(2), resetTimer: noBool, repetitions: noNumber }], - address: noString, - salt: noString, - } - - let result: SequentialDelayResult | undefined - result = checkSequentialDelayRateLimit(domain, t + 1, result?.state) - expect(result).toEqual({ - accepted: true, - state: { timer: t + 1, counter: 1, disabled: false, now: t + 1 }, - }) - - // Set the domain to disabled and attempt to make another reqeust. - result!.state!.disabled = true - result = checkSequentialDelayRateLimit(domain, t + 1, result?.state) - expect(result).toEqual({ - accepted: false, - notBefore: undefined, - state: { timer: t + 1, counter: 1, disabled: true, now: t + 1 }, - }) - }) - - it('should accumulate quota when resetTimer is false', () => { - const t = 10 // initial delay - - const domain: SequentialDelayDomain = { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [ - { delay: t, resetTimer: defined(false), batchSize: noNumber, repetitions: noNumber }, - { delay: 1, resetTimer: defined(false), batchSize: noNumber, repetitions: noNumber }, - { delay: 1, resetTimer: defined(false), batchSize: noNumber, repetitions: noNumber }, - { delay: 1, resetTimer: defined(false), batchSize: noNumber, repetitions: noNumber }, - ], - address: noString, - salt: noString, - } - - const attempts: TestAttempt[] = [ - { - timestamp: t + 3, - expectedResult: { - accepted: true, - state: { timer: t, counter: 1, disabled: false, now: t + 3 }, - }, - }, - { - timestamp: t + 3, - expectedResult: { - accepted: true, - state: { timer: t + 1, counter: 2, disabled: false, now: t + 3 }, - }, - }, - { - timestamp: t + 3, - expectedResult: { - accepted: true, - state: { timer: t + 2, counter: 3, disabled: false, now: t + 3 }, - }, - }, - { - timestamp: t + 3, - expectedResult: { - accepted: true, - state: { timer: t + 3, counter: 4, disabled: false, now: t + 3 }, - }, - }, - { - timestamp: t + 3, - expectedResult: { - accepted: false, - notBefore: undefined, - state: { timer: t + 3, counter: 4, disabled: false, now: t + 3 }, - }, - }, - ] - - checkTestAttempts(t, domain, attempts) - }) - - it('should not accumulate quota when resetTimer is true', () => { - const t = 0 // initial delay - - const domain: SequentialDelayDomain = { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [ - { delay: t, resetTimer: noBool, batchSize: noNumber, repetitions: noNumber }, - { delay: 1, resetTimer: noBool, batchSize: noNumber, repetitions: noNumber }, - ], - address: noString, - salt: noString, - } - - const attempts: TestAttempt[] = [ - { - timestamp: t + 2, - expectedResult: { - accepted: true, - state: { timer: t + 2, counter: 1, disabled: false, now: t + 2 }, - }, - }, - { - timestamp: t + 2, - expectedResult: { - accepted: false, - notBefore: t + 3, - state: { timer: t + 2, counter: 1, disabled: false, now: t + 2 }, - }, - }, - { - timestamp: t + 3, - expectedResult: { - accepted: true, - state: { timer: t + 3, counter: 2, disabled: false, now: t + 3 }, - }, - }, - ] - - checkTestAttempts(t, domain, attempts) - }) - - it('should return the correct results in the example sequence', () => { - const t = 10 // initial delay - - const domain: SequentialDelayDomain = { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [ - { delay: t, resetTimer: noBool, batchSize: defined(2), repetitions: noNumber }, - { delay: 1, resetTimer: defined(false), batchSize: noNumber, repetitions: noNumber }, - { delay: 1, resetTimer: defined(true), batchSize: noNumber, repetitions: noNumber }, - { delay: 2, resetTimer: defined(false), batchSize: noNumber, repetitions: defined(1) }, - { delay: 4, resetTimer: noBool, batchSize: defined(2), repetitions: defined(2) }, - ], - address: noString, - salt: noString, - } - - const attempts: TestAttempt[] = [ - { - timestamp: t - 1, - expectedResult: { - accepted: false, - notBefore: t, - state: { timer: 0, counter: 0, disabled: false, now: t - 1 }, - }, - }, - { - timestamp: t, - expectedResult: { - accepted: true, - state: { timer: t, counter: 1, disabled: false, now: t }, - }, - }, - { - timestamp: t + 1, - expectedResult: { - accepted: true, - state: { timer: t + 1, counter: 2, disabled: false, now: t + 1 }, - }, - }, - { - timestamp: t + 3, - expectedResult: { - accepted: true, - state: { timer: t + 2, counter: 3, disabled: false, now: t + 3 }, - }, - }, - { - timestamp: t + 3, - expectedResult: { - accepted: true, - state: { timer: t + 3, counter: 4, disabled: false, now: t + 3 }, - }, - }, - { - timestamp: t + 6, - expectedResult: { - accepted: true, - state: { timer: t + 5, counter: 5, disabled: false, now: t + 6 }, - }, - }, - { - timestamp: t + 8, - expectedResult: { - accepted: false, - notBefore: t + 9, - state: { timer: t + 5, counter: 5, disabled: false, now: t + 8 }, - }, - }, - { - timestamp: t + 9, - expectedResult: { - accepted: true, - state: { timer: t + 9, counter: 6, disabled: false, now: t + 9 }, - }, - }, - { - timestamp: t + 10, - expectedResult: { - accepted: true, - state: { timer: t + 10, counter: 7, disabled: false, now: t + 10 }, - }, - }, - { - timestamp: t + 14, - expectedResult: { - accepted: true, - state: { timer: t + 14, counter: 8, disabled: false, now: t + 14 }, - }, - }, - { - timestamp: t + 15, - expectedResult: { - accepted: true, - state: { timer: t + 15, counter: 9, disabled: false, now: t + 15 }, - }, - }, - ] - - checkTestAttempts(t, domain, attempts) - }) - }) -}) diff --git a/packages/phone-number-privacy/common/tsconfig.json b/packages/phone-number-privacy/common/tsconfig.json deleted file mode 100644 index 1f3b359f10..0000000000 --- a/packages/phone-number-privacy/common/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "plugins": [ - { - "name": "typescript-tslint-plugin" - } - ], - "lib": ["es2017"], - "module": "commonjs", - "strict": true, - "allowJs": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": true, - "target": "es2017", - "rootDir": "src", - "outDir": "./lib", - "skipLibCheck": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "preserveConstEnums": true, - "composite": true - }, - "include": ["src", "index.d.ts"], - "compileOnSave": true -} diff --git a/packages/phone-number-privacy/common/tslint.json b/packages/phone-number-privacy/common/tslint.json deleted file mode 100644 index 5fc86ecb71..0000000000 --- a/packages/phone-number-privacy/common/tslint.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": ["@celo/typescript/tslint.json"], - "rules": { - "no-global-arrow-functions": false, - "no-console": true - } -} diff --git a/packages/phone-number-privacy/monitor/.env b/packages/phone-number-privacy/monitor/.env deleted file mode 100644 index 7d00d584c4..0000000000 --- a/packages/phone-number-privacy/monitor/.env +++ /dev/null @@ -1,12 +0,0 @@ -PHONE_NUMBER='+14155550123' - -PRIVATE_KEY='0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' - -# Options: json, human (default), stackdriver -LOG_FORMAT=stackdriver -# Options: fatal, error, warn, info (default), debug, trace -LOG_LEVEL=info -SERVICE_NAME='odis-monitor' - -BLOCKCHAIN_PROVIDER="https://alfajores-forno.celo-testnet.org" -NETWORK='alfajores' \ No newline at end of file diff --git a/packages/phone-number-privacy/monitor/.firebaserc b/packages/phone-number-privacy/monitor/.firebaserc deleted file mode 100644 index b8893af884..0000000000 --- a/packages/phone-number-privacy/monitor/.firebaserc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "projects": { - "default": "celo-phone-number-privacy" - } -} diff --git a/packages/phone-number-privacy/monitor/.gitignore b/packages/phone-number-privacy/monitor/.gitignore deleted file mode 100644 index bad9645de0..0000000000 --- a/packages/phone-number-privacy/monitor/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -dist/ - -# Firebase cache -.firebase/ -firebase-debug.log diff --git a/packages/phone-number-privacy/monitor/README.md b/packages/phone-number-privacy/monitor/README.md deleted file mode 100644 index c538bb9ce5..0000000000 --- a/packages/phone-number-privacy/monitor/README.md +++ /dev/null @@ -1,3 +0,0 @@ -## ODIS Monitor - -A firebase schedule function that monitors ODIS by regularly querying the combiner. diff --git a/packages/phone-number-privacy/monitor/firebase.json b/packages/phone-number-privacy/monitor/firebase.json deleted file mode 100644 index 028b3af257..0000000000 --- a/packages/phone-number-privacy/monitor/firebase.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "functions": { - "source": ".", - "predeploy": ["yarn run lint", "yarn run build"] - } -} diff --git a/packages/phone-number-privacy/monitor/package.json b/packages/phone-number-privacy/monitor/package.json deleted file mode 100644 index a3e02aeeff..0000000000 --- a/packages/phone-number-privacy/monitor/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@celo/phone-number-privacy-monitor", - "version": "3.0.1", - "description": "Regularly queries ODIS to ensure the system is functioning properly", - "author": "Celo", - "license": "Apache-2.0", - "main": "dist/index.js", - "scripts": { - "deploy": "yarn build && firebase deploy --only functions:odisMonitorScheduleFunctionLegacyPNP,functions:odisMonitorScheduleFunctionPNP,functions:odisMonitorScheduleFunctionDomains", - "deploy:staging": "yarn deploy --project celo-phone-number-privacy-stg", - "deploy:alfajores": "yarn deploy --project celo-phone-number-privacy", - "deploy:mainnet": "yarn deploy --project celo-pgpnp-mainnet", - "config:get:staging": "firebase functions:config:get --project celo-phone-number-privacy-stg", - "config:get:alfajores": "firebase functions:config:get --project celo-phone-number-privacy", - "config:get:mainnet": "firebase functions:config:get --project celo-pgpnp-mainnet", - "config:set:staging": "firebase functions:config:set --project celo-phone-number-privacy-stg", - "config:set:alfajores": "firebase functions:config:set --project celo-phone-number-privacy", - "config:set:mainnet": "firebase functions:config:set --project celo-pgpnp-mainnet", - "clean": "tsc -b . --clean", - "build": "tsc -b .", - "lint": "tslint --project .", - "loadTest": "ts-node src/scripts/run-load-test.ts run" - }, - "dependencies": { - "@celo/base": "^5.0.4", - "@celo/contractkit": "^5.0.4", - "@celo/cryptographic-utils": "^5.0.4", - "@celo/encrypted-backup": "^5.0.4", - "@celo/identity": "^5.0.4", - "@celo/wallet-local": "^5.0.4", - "@celo/phone-number-privacy-common": "^3.0.3", - "@celo/utils": "^5.0.4", - "yargs": "^14.0.0", - "firebase-admin": "^9.12.0", - "firebase-functions": "^3.15.7" - }, - "devDependencies": { - "firebase-functions-test": "^0.3.3", - "firebase-tools": "9.20.0" - }, - "engines": { - "node": ">=14" - } -} \ No newline at end of file diff --git a/packages/phone-number-privacy/monitor/src/index.ts b/packages/phone-number-privacy/monitor/src/index.ts deleted file mode 100644 index 8fddb7fa34..0000000000 --- a/packages/phone-number-privacy/monitor/src/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as functions from 'firebase-functions' -import { testDomainSignQuery, testPNPSignQuery } from './test' - -const contextName = functions.config().monitor.context_name -const blockchainProvider = functions.config().blockchain.provider -if (!contextName || !blockchainProvider) { - throw new Error('blockchain provider and context name must be set in function config') -} - -export const odisMonitorScheduleFunctionPNP = functions - .region('us-central1') - .pubsub.schedule('every 5 minutes') - .onRun(async () => testPNPSignQuery(blockchainProvider, contextName)) - -export const odisMonitorScheduleFunctionDomains = functions - .region('us-central1') - .pubsub.schedule('every 5 minutes') - .onRun(async () => testDomainSignQuery(contextName)) diff --git a/packages/phone-number-privacy/monitor/src/query.ts b/packages/phone-number-privacy/monitor/src/query.ts deleted file mode 100644 index 955b41c3b5..0000000000 --- a/packages/phone-number-privacy/monitor/src/query.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { newKit } from '@celo/contractkit' -import { generateKeys, generateMnemonic, MnemonicStrength } from '@celo/cryptographic-utils' -import { - buildOdisDomain, - OdisHardeningConfig, - odisHardenKey, - odisQueryAuthorizer, -} from '@celo/encrypted-backup' -import { OdisUtils } from '@celo/identity' -import { - AuthSigner, - getServiceContext, - OdisAPI, - OdisContextName, -} from '@celo/identity/lib/odis/query' -import { fetchEnv } from '@celo/phone-number-privacy-common' -import { genSessionID } from '@celo/phone-number-privacy-common/lib/utils/logger' -import { normalizeAddressWith0x, privateKeyToAddress } from '@celo/utils/lib/address' -import { defined } from '@celo/utils/lib/sign-typed-data-utils' -import { LocalWallet } from '@celo/wallet-local' -import { dekAuthSigner, generateRandomPhoneNumber, PRIVATE_KEY } from './resources' - -let phoneNumber = fetchEnv('PHONE_NUMBER') - -const newPrivateKey = async () => { - const mnemonic = await generateMnemonic(MnemonicStrength.s256_24words) - return (await generateKeys(mnemonic)).privateKey -} - -export const queryOdisForSalt = async ( - blockchainProvider: string, - contextName: OdisContextName, - timeoutMs: number = 10000, - bypassQuota: boolean = false, - useDEK: boolean = false, - privateKey?: string, - privateKeyPercentage: number = 100 -) => { - let authSigner: AuthSigner - let accountAddress: string - - const serviceContext = getServiceContext(contextName, OdisAPI.PNP) - - const contractKit = newKit(blockchainProvider, new LocalWallet()) - - if (useDEK) { - if (!privateKey || Math.random() > privateKeyPercentage * 0.01) { - privateKey = PRIVATE_KEY - } - contractKit.connection.addAccount(privateKey) - accountAddress = normalizeAddressWith0x(privateKeyToAddress(privateKey)) - contractKit.defaultAccount = accountAddress - authSigner = dekAuthSigner(0) - phoneNumber = generateRandomPhoneNumber() - } else { - if (!privateKey || Math.random() > privateKeyPercentage * 0.01) { - privateKey = await newPrivateKey() - } - accountAddress = normalizeAddressWith0x(privateKeyToAddress(privateKey)) - contractKit.connection.addAccount(privateKey) - contractKit.defaultAccount = accountAddress - authSigner = { - authenticationMethod: OdisUtils.Query.AuthenticationMethod.WALLET_KEY, - contractKit, - } - } - - const abortController = new AbortController() - const timeout = setTimeout(() => { - abortController.abort() - console.log(`ODIS salt request timed out after ${timeoutMs} ms`) // tslint:disable-line:no-console - }, timeoutMs) - try { - const testSessionId = Math.floor(Math.random() * 100000).toString() - const res = await OdisUtils.Identifier.getObfuscatedIdentifier( - phoneNumber, - OdisUtils.Identifier.IdentifierPrefix.PHONE_NUMBER, - accountAddress, - authSigner, - serviceContext, - undefined, - undefined, - undefined, - bypassQuota ? testSessionId : genSessionID(), - undefined, - abortController - ) - clearTimeout(timeout) - - return res - } catch (error) { - clearTimeout(timeout) - throw error - } -} - -export const queryOdisForQuota = async ( - blockchainProvider: string, - contextName: OdisContextName, - timeoutMs: number = 10000, - privateKey?: string, - privateKeyPercentage: number = 100 -) => { - console.log(`contextName: ${contextName}`) // tslint:disable-line:no-console - console.log(`blockchain provider: ${blockchainProvider}`) // tslint:disable-line:no-console - - const serviceContext = getServiceContext(contextName, OdisAPI.PNP) - - const contractKit = newKit(blockchainProvider, new LocalWallet()) - - if (!privateKey || Math.random() > privateKeyPercentage * 0.01) { - privateKey = await newPrivateKey() - } - const accountAddress = normalizeAddressWith0x(privateKeyToAddress(privateKey)) - contractKit.connection.addAccount(privateKey) - contractKit.defaultAccount = accountAddress - const authSigner: AuthSigner = { - authenticationMethod: OdisUtils.Query.AuthenticationMethod.WALLET_KEY, - contractKit, - } - - const abortController = new AbortController() - const timeout = setTimeout(() => { - abortController.abort() - }, timeoutMs) - - try { - const res = await OdisUtils.Quota.getPnpQuotaStatus( - accountAddress, - authSigner, - serviceContext, - undefined, - undefined, - abortController - ) - - clearTimeout(timeout) - - return res - } catch (error) { - clearTimeout(timeout) - throw error - } -} - -export const queryOdisDomain = async (contextName: OdisContextName) => { - console.log(`contextName: ${contextName}`) // tslint:disable-line:no-console - - const serviceContext = getServiceContext(contextName, OdisAPI.DOMAIN) - const monitorDomainConfig: OdisHardeningConfig = { - rateLimit: [ - { - delay: 0, - resetTimer: defined(true), - // Running every 5 min, this should not run out for the next 9 million years - batchSize: defined(1000000000000), - repetitions: defined(1000000000000), - }, - ], - environment: serviceContext, - } - const authorizer = odisQueryAuthorizer(Buffer.from('ODIS domains monitor authorizer test seed')) - const domain = buildOdisDomain(monitorDomainConfig, authorizer.address) - // Throws if signature verification fails - return odisHardenKey(Buffer.from('password'), domain, serviceContext, authorizer.wallet) -} diff --git a/packages/phone-number-privacy/monitor/src/resources.ts b/packages/phone-number-privacy/monitor/src/resources.ts deleted file mode 100644 index cdd0713da3..0000000000 --- a/packages/phone-number-privacy/monitor/src/resources.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { EncryptionKeySigner } from '@celo/identity/lib/odis/query' -import { AuthenticationMethod } from '@celo/phone-number-privacy-common' -import { - ensureLeading0x, - normalizeAddressWith0x, - privateKeyToAddress, -} from '@celo/utils/lib/address' - -export const PRIVATE_KEY = '2c63bf6d60b16c8afa13e1069dbe92fef337c23855fff8b27732b3e9c6e7efd4' -export const ACCOUNT_ADDRESS = normalizeAddressWith0x(privateKeyToAddress(PRIVATE_KEY)) // 0x6037800e91eaa703e38bad40c01410bbdf0fea7e - -interface DEK { - privateKey: string - publicKey: string - address: string -} - -export const deks: DEK[] = [ - { - privateKey: 'bf8a2b73baf8402f8fe906ad3f42b560bf14b39f7df7797ece9e293d6f162188', - publicKey: '034846bc781cacdafc66f3a77aa9fc3c56a9dadcd683c72be3c446fee8da041070', - address: '0x7b33dF2607b85e3211738a49A6Ad6E8Ed4d13F6E', - }, - { - privateKey: '0975b0c565abc75b6638a749ea3008cb52676af3eabe4b80e19c516d82330364', - publicKey: '03b1ac8c445f0796978018c087b97e8213b32c39e6a8642ae63dce71da33a19f65', - address: '0x34332049B07Fab9a2e843A7C8991469d93cF6Ae6', - }, -] - -// The following code can be used to generate more test DEKs -// const generateDEKs = (n: number): Promise => Promise.all([...Array(n).keys()].map( -// async () => await deriveDek(await generateMnemonic()) -// )) - -export const dekAuthSigner = (index: number): EncryptionKeySigner => { - return { - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - rawKey: ensureLeading0x(deks[index].privateKey), - } -} - -export function generateRandomPhoneNumber() { - const min = 1000000000 // Smallest 10-digit number - const max = 9999999999 // Largest 10-digit number - const randomNumber = Math.floor(Math.random() * (max - min + 1)) + min - return '+1' + randomNumber.toString() -} diff --git a/packages/phone-number-privacy/monitor/src/scripts/run-load-test.ts b/packages/phone-number-privacy/monitor/src/scripts/run-load-test.ts deleted file mode 100644 index e2b2d09825..0000000000 --- a/packages/phone-number-privacy/monitor/src/scripts/run-load-test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { OdisContextName } from '@celo/identity/lib/odis/query' -import { CombinerEndpointPNP, rootLogger } from '@celo/phone-number-privacy-common' -import yargs from 'yargs' -import { concurrentRPSLoadTest } from '../test' - -const logger = rootLogger('odis-monitor') - -// tslint:disable-next-line: no-unused-expression -yargs - .scriptName('ODIS-load-test') - .recommendCommands() - .demandCommand(1) - .strict(true) - .showHelpOnFail(true) - .command( - 'run ', - 'Load test ODIS.', - (args) => - args - .positional('contextName', { - type: 'string', - description: 'Desired network.', - }) - .positional('rps', { - type: 'number', - description: 'Number of requests per second to generate', - }) - .option('duration', { - type: 'number', - description: 'Duration of the loadtest in Ms.', - default: 0, - }) - .option('bypassQuota', { - type: 'boolean', - description: 'Bypass Signer quota check.', - default: false, - }) - .option('useDEK', { - type: 'boolean', - description: 'Use Data Encryption Key (DEK) to authenticate.', - default: false, - }) - .option('movingAvgRequests', { - type: 'number', - description: 'number of requests to use when calculating latency moving average', - default: 50, - }) - .option('privateKey', { - type: 'string', - description: 'optional private key to send requests from', - }) - .option('privateKeyPercentage', { - type: 'number', - description: 'percentage of time to use privateKey, if specified', - default: 100, - }), - (args) => { - if (args.rps == null || args.contextName == null) { - logger.error('missing positional arguments') - yargs.showHelp() - process.exit(1) - } - const rps = args.rps! - const contextName = args.contextName! as OdisContextName - - let blockchainProvider: string - switch (contextName) { - case 'alfajoresstaging': - case 'alfajores': - blockchainProvider = 'https://alfajores-forno.celo-testnet.org' - break - case 'mainnet': - blockchainProvider = 'https://forno.celo.org' - break - default: - logger.error('Invalid contextName') - yargs.showHelp() - process.exit(1) - } - - if (rps < 1) { - logger.error('Invalid rps') - yargs.showHelp() - process.exit(1) - } - concurrentRPSLoadTest( - args.rps, - blockchainProvider!, - contextName, - CombinerEndpointPNP.PNP_SIGN, - args.duration, - args.bypassQuota, - args.useDEK, - args.movingAvgRequests, - args.privateKey, - args.privateKeyPercentage - ) // tslint:disable-line:no-floating-promises - } - ).argv diff --git a/packages/phone-number-privacy/monitor/src/test.ts b/packages/phone-number-privacy/monitor/src/test.ts deleted file mode 100644 index cd92641532..0000000000 --- a/packages/phone-number-privacy/monitor/src/test.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { sleep } from '@celo/base' -import { Result } from '@celo/base/lib/result' -import { BackupError } from '@celo/encrypted-backup' -import { IdentifierHashDetails } from '@celo/identity/lib/odis/identifier' -import { ErrorMessages, OdisContextName } from '@celo/identity/lib/odis/query' -import { PnpClientQuotaStatus } from '@celo/identity/lib/odis/quota' -import { CombinerEndpointPNP, rootLogger } from '@celo/phone-number-privacy-common' -import { performance } from 'perf_hooks' -import { queryOdisDomain, queryOdisForQuota, queryOdisForSalt } from './query' - -const logger = rootLogger('odis-monitor') - -export async function testPNPSignQuery( - blockchainProvider: string, - contextName: OdisContextName, - timeoutMs?: number, - bypassQuota?: boolean, - useDEK?: boolean, - privateKey?: string, - privateKeyPercentage: number = 100 -) { - try { - const odisResponse: IdentifierHashDetails = await queryOdisForSalt( - blockchainProvider, - contextName, - timeoutMs, - bypassQuota, - useDEK, - privateKey, - privateKeyPercentage - ) - logger.debug({ odisResponse }, 'ODIS salt request successful. System is healthy.') - } catch (err) { - if ((err as Error).message === ErrorMessages.ODIS_QUOTA_ERROR) { - logger.warn( - { error: err }, - 'ODIS salt request out of quota. This is expected. System is healthy.' - ) - } else { - logger.error('ODIS salt request failed.') - logger.error({ err }) - throw err - } - } -} - -export async function testPNPQuotaQuery( - blockchainProvider: string, - contextName: OdisContextName, - timeoutMs?: number, - privateKey?: string, - privateKeyPercentage: number = 100 -) { - logger.info(`Performing test PNP query for ${CombinerEndpointPNP.PNP_QUOTA}`) - try { - const odisResponse: PnpClientQuotaStatus = await queryOdisForQuota( - blockchainProvider, - contextName, - timeoutMs, - privateKey, - privateKeyPercentage - ) - logger.info({ odisResponse }, 'ODIS quota request successful. System is healthy.') - } catch (err) { - logger.error('ODIS quota request failed.') - logger.error({ err }) - throw err - } -} - -export async function testDomainSignQuery(contextName: OdisContextName) { - logger.info('Performing test domains query') - let odisResponse: Result - try { - odisResponse = await queryOdisDomain(contextName) - logger.info({ odisResponse }, 'ODIS response') - } catch (err) { - logger.error('ODIS key hardening request failed.') - logger.error({ err }) - throw err - } - if (odisResponse.ok) { - logger.info('System is healthy') - } else { - throw new Error('Received not ok response') - } -} - -export async function concurrentRPSLoadTest( - rps: number, - blockchainProvider: string, - contextName: OdisContextName, - endpoint: - | CombinerEndpointPNP.PNP_QUOTA - | CombinerEndpointPNP.PNP_SIGN = CombinerEndpointPNP.PNP_SIGN, - duration: number = 0, - bypassQuota: boolean = false, - useDEK: boolean = false, - movingAverageRequests: number = 50, - privateKey?: string, - privateKeyPercentage: number = 100 -) { - const latencyQueue: number[] = [] - let movingAvgLatencySum = 0 - let latencySum = 0 - let index = 1 - - function measureLatency(fn: () => Promise): () => Promise { - return async () => { - const start = performance.now() - - await fn() - - const reqLatency = performance.now() - start - latencySum += reqLatency - movingAvgLatencySum += reqLatency - - const queuelength = latencyQueue.push(reqLatency) - if (queuelength > movingAverageRequests) { - movingAvgLatencySum -= latencyQueue.shift()! - } - - const stats = { - averageLatency: Math.round(latencySum / index), - movingAverageLatency: Math.round(movingAvgLatencySum / latencyQueue.length), - index, - } - - if (reqLatency > 600) { - logger.warn(stats, 'SLOW Request') - } else { - logger.info(stats, 'request finished') - } - index++ - } - } - - const testFn = async () => { - try { - await (endpoint === CombinerEndpointPNP.PNP_SIGN - ? testPNPSignQuery( - blockchainProvider, - contextName, - undefined, - bypassQuota, - useDEK, - privateKey, - privateKeyPercentage - ) - : testPNPQuotaQuery( - blockchainProvider, - contextName, - undefined, - privateKey, - privateKeyPercentage - )) - } catch (_) { - logger.error('load test request failed') - } - } - - return doRPSTest(measureLatency(testFn), rps, duration) -} - -async function doRPSTest( - testFn: () => Promise, - rps: number, - duration: number = 0 -): Promise { - const inFlightRequests: Array> = [] - let shouldRun = true - - async function requestSender() { - while (shouldRun) { - for (let i = 0; i < rps; i++) { - inFlightRequests.push(testFn()) - } - await sleep(1000) - } - } - - async function requestEnder() { - while (shouldRun || inFlightRequests.length > 0) { - if (inFlightRequests.length > 0) { - const req = inFlightRequests.shift() - await req?.catch((_err) => { - logger.error('load test request failed') - }) - } else { - await sleep(1000) - } - } - } - - async function durationChecker() { - await sleep(duration) - shouldRun = false - } - - if (duration === 0) { - await Promise.all([requestSender(), requestEnder()]) - } else { - await Promise.all([durationChecker(), requestSender(), requestEnder()]) - } -} diff --git a/packages/phone-number-privacy/monitor/tsconfig.json b/packages/phone-number-privacy/monitor/tsconfig.json deleted file mode 100644 index 5a050f2a77..0000000000 --- a/packages/phone-number-privacy/monitor/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "plugins": [ - { - "name": "typescript-tslint-plugin" - } - ], - "lib": ["es2017"], - "module": "commonjs", - "strict": true, - "allowJs": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": true, - "target": "es2017", - "rootDir": "src", - "outDir": "./dist", - "skipLibCheck": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "preserveConstEnums": true, - "composite": true - }, - "include": ["src", "index.d.ts"], - "compileOnSave": true -} diff --git a/packages/phone-number-privacy/monitor/tslint.json b/packages/phone-number-privacy/monitor/tslint.json deleted file mode 100644 index 5fc86ecb71..0000000000 --- a/packages/phone-number-privacy/monitor/tslint.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": ["@celo/typescript/tslint.json"], - "rules": { - "no-global-arrow-functions": false, - "no-console": true - } -} diff --git a/packages/phone-number-privacy/monitor/ui-debug.log b/packages/phone-number-privacy/monitor/ui-debug.log deleted file mode 100644 index 2f9d04d84c..0000000000 --- a/packages/phone-number-privacy/monitor/ui-debug.log +++ /dev/null @@ -1 +0,0 @@ -Web / API server started at http://localhost:4000 diff --git a/packages/phone-number-privacy/signer/.env b/packages/phone-number-privacy/signer/.env deleted file mode 100644 index 17849657cd..0000000000 --- a/packages/phone-number-privacy/signer/.env +++ /dev/null @@ -1,45 +0,0 @@ -NODE_ENV=development -#SERVER_PORT=443 -#SERVER_SSL_KEY_PATH=./server.key -#SERVER_SSL_CERT_PATH=./server.cert -BLOCKCHAIN_PROVIDER=https://alfajores-forno.celo-testnet.org -DB_HOST=http://localhost -DB_USERNAME=postgres -DB_PASSWORD=mockPass -DB_DATABASE=phoneNumberPrivacy -DB_USE_SSL=true -#KEYSTORE_TYPE=AzureKeyVault -#KEYSTORE_AZURE_CLIENT_ID=useMock -#KEYSTORE_AZURE_CLIENT_SECRET=useMock -#KEYSTORE_AZURE_TENANT=useMock -#KEYSTORE_AZURE_VAULT_NAME=useMock -PHONE_NUMBER_PRIVACY_KEY_NAME_BASE='phoneNumberPrivacy' -DOMAINS_KEY_NAME_BASE='domains' -PHONE_NUMBER_PRIVACY_LATEST_KEY_VERSION='1' -DOMAINS_LATEST_KEY_VERSION='1' -KEYSTORE_TYPE=MockSecretManager -KEYSTORE_GOOGLE_PROJECT_ID=mockProjectId -# Options: json, human (default), stackdriver -LOG_FORMAT=stackdriver -# Options: fatal, error, warn, info (default), debug, trace -LOG_LEVEL=info -SERVICE_NAME='odis-signer' - -# For e2e Tests -ODIS_SIGNER_SERVICE_URL="SIGNER OPERATORS FILL IN" -STAGING_ODIS_BLOCKCHAIN_PROVIDER=https://alfajores-forno.celo-testnet.org -ALFAJORES_ODIS_BLOCKCHAIN_PROVIDER=https://alfajores-forno.celo-testnet.org -MAINNET_ODIS_BLOCKCHAIN_PROVIDER=https://forno.celo.org -ODIS_DOMAINS_TEST_KEY_VERSION=1 -ODIS_PNP_TEST_KEY_VERSION=1 -DEPLOYED_SIGNER_SERVICE_VERSION=3.0.1 -# PUBKEYS -STAGING_DOMAINS_PUBKEY=7FsWGsFnmVvRfMDpzz95Np76wf/1sPaK0Og9yiB+P8QbjiC8FV67NBans9hzZEkBaQMhiapzgMR6CkZIZPvgwQboAxl65JWRZecGe5V3XO4sdKeNemdAZ2TzQuWkuZoA -ALFAJORES_DOMAINS_PUBKEY=+ZrxyPvLChWUX/DyPw6TuGwQH0glDJEbSrSxUARyP5PuqYyP/U4WZTV1e0bAUioBZ6QCJMiLpDwTaFvy8VnmM5RBbLQUMrMg5p4+CBCqj6HhsMfcyUj8V0LyuNdStlCB -MAINNET_DOMAINS_PUBKEY=LX4tLiuYm8geZ3ztmH7oIWz4ohXt3ePRTd9BbG9RO86NMrApflioiOzKYtIsyjEA0uarnX8Emo+luTY4bwEWpgZDyPYE6UMWAoBaZBdy6NDMgAxSbdNtaQEq51fBjCUA -# Polynomials -STAGING_POLYNOMIAL=0200000000000000ec5b161ac167995bd17cc0e9cf3f79369efac1fff5b0f68ad0e83dca207e3fc41b8e20bc155ebb3416a7b3d87364490169032189aa7380c47a0a464864fbe0c106e803197ae4959165e7067b95775cee2c74a78d7a67406764f342e5a4b99a003a510287524c9437b12ebb0bfdc7ea46078b807d1b665966961784bd71c4227c272b01c0fcd19c5b92226c1aac324b010abef36192e8ff3abb25686b3e6707bc747b129c32e572b5850db8446bd8f0af9a3fbf6b579793002b1b68528ca4ac00 -ALFAJORES_PHONE_NUMBER_PRIVACY_POLYNOMIAL=020000000000000090fa11c56744759fcd777b909e9dc5245b39e33ba24be92caaf3a6f71f2f63a2873c6f23adfab2b2f211534c2da98b01280ed2a00b0808c06ff02fc56f66690ceaa14aabebfec65b6681e641fbdbaabcdbb4320fbd422b1e0452d3274908cb00f3d2ba1d64ddc12f387ef5c6fb98265cee27afa66626edf91b9839d49f23890d75a550a49a2e7a75b06b3b49734a160035558eb2079c41926388ac560e75f1962dada39e5c30ba35bef59eb84ff4329432cdc10383b4dea40f5ad8fabbb09a81 -MAINNET_PHONE_NUMBER_PRIVACY_POLYNOMIAL=060000000000000016fade1df2e68418f0c47c6cc5ecab70e2ed4a89c2f63ecadd6ad2e106a962c407e8b75a0d368d1a69e540c7c5634e01a7f2b8c00bea4303bdfdba8f54229ff197bc399a3c16b9a8838258e31022c2bb2a397c6e835d7e86d8c47b5a63e2e30017f865337fd0060497457135173e2b0eaec6f8f14f0cacb17a5d150218e15bd46963ed1b9d56f956f9c4fc692813100042f098b7f70913f671e28ed1c99104b9b740549c42c59212b6671f1e1675674f7e6b6d690a13bd474ab9f0c83cd48e017514ca3874606f6abde2b957c791376e24d55efe6ccc7a1194a685b9589ca873a51c7e77b7b814a76cd9af2aafef500155280fb84efd3219b04312635568788b3393fd45a11f431a7eef8a8fc59ff2bfd4aab744baf9221bf1774653dda61d8193b720f60c627d5a9fec5c2c16a27e948f2f4545b460090303327262ec87f51fbf860f58d5e051d91d5bb869c8912300a9b1c2d922d329c9b7d5179946e049d52ed9b3876f36e5c8b2a47831eb235a51d8d877a284fbe07750449f9654d332808beb9641404188813cddb8ffad906752d71f3f042b583f501b3b7f3906946f9931c598575bf4c8d3e8941168f8cc8e001c092117257bb073db3885dffca5e8dd76b689d395bb5555cf00f9943a9e1ec9939f9d700407330163220f3c15a9420011b8693fb95c635168b6b0a021263b246301343e80161eac44fe79ba657fe59deb9d297ced18d090a8f65dc9c2e0990177f186d7501a2256ac9ecca36743e118f5dd4ce35dc976d38c8679d53cd11b0f11edb45c3473ce848d35875e63b2d100 -ALFAJORES_DOMAINS_POLYNOMIAL=0200000000000000f99af1c8fbcb0a15945ff0f23f0e93b86c101f48250c911b4ab4b15004723f93eea98c8ffd4e166535757b46c0522a0167a40224c88ba43c13685bf2f159e63394416cb41432b320e69e3e0810aa8fa1e1b0c7dcc948fc5742f2b8d752b65081f10d83821b4e2cf90b56cc4fc8c98dc00e5f24f2c5b53fa8ad7c2ebd3963c9223cf95209692d267a4f8084edfc0b5f01f7a31d82bf5421c544b6258749c691b79e6f36d9ba963ead6f25b9986b6bcb7d45b5edb33a616af630b4ce17bf552c81 -MAINNET_DOMAINS_POLYNOMIAL=05000000000000002d7e2d2e2b989bc81e677ced987ee8216cf8a215eddde3d14ddf416c6f513bce8d32b0297e58a888ecca62d22cca3100d2e6ab9d7f049a8fa5b936386f0116a60643c8f604e9431602805a641772e8d0cc800c526dd36d69012ae757c18c250029d97c8a3d4b81e305780b49d511c80dc3009c02b8f651a06c8ec2d5530937a1f7eadf730ad46762a4c089bbd973a000ba77717ec36ebb6fd58904b444a6cde7dd3b3b7ac6fa37f9cd8d00aa67e7cfe81adee5ed45218f7f78b4f8473b564601f4361d228dc6dabf7decd3f61f5bb0ad2c7bd7fe5b7a88054959543e82f4deb08d4fe9af4ac775c9353e038e79f82200863ac9cb7fd6b5fa263eb9d1dead51002607f3eadac153596b671b854715bdb07bee1b0bc8d5178f0dac1b4d00ed0700f46e37135e96604d389f3a323028e29b07f36279e829da00eee1794f3ad6e5dca24eba65a7821755cc464add27c7a601c7e187756e79a5ec3c847f4d91b037fe3cd40590fc1a46b46c2f68c0edcbe5cd7727162a195a711008e4e956eb8a81011b290057cee3f14b9a4198a3e9909cac69a9e7d648fa3dd185794acc4c1e4b994637dca36621d463b42e015115ac2c015fc176d8f143bf99cca654ae95a3101afbdc0c5026f95fbf31af1ac115399f5b6b6d1de09af367745415be9533f8c080 diff --git a/packages/phone-number-privacy/signer/.gitignore b/packages/phone-number-privacy/signer/.gitignore deleted file mode 100644 index 4b329a550c..0000000000 --- a/packages/phone-number-privacy/signer/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -dist/ - -# Firebase cache -.firebase/ - -gcp-service-account.json -server.cert -server.key - -# Ignore updates for dev deployments -./azure-templates/container-parameters.json - -package-lock.json \ No newline at end of file diff --git a/packages/phone-number-privacy/signer/README.md b/packages/phone-number-privacy/signer/README.md deleted file mode 100644 index 6e4ee0f8fc..0000000000 --- a/packages/phone-number-privacy/signer/README.md +++ /dev/null @@ -1,177 +0,0 @@ -# ODIS - Signer Service - -A service that generates unique partial signatures for blinded messages. Using a threshold BLS signature scheme, when K/N signatures are combined, a deterministic signature is obtained. - -## APIs (ODIS v2) - -ODIS v2 provides support for three APIs, which need to be explicitly enabled ([see below](#enabling-apis-odis-v2) for configuration info): - -- **PNP API**: retrieve signatures for blinded messages, rate-limited based on quota purchased on-chain in `OdisPayments.sol`. -- **Domains API**: retrieve signatures over domains with custom rate-limiting schemes, as defined in more detail in [CIP-40](https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0040.md). - -## Configuration - -You can use the following environment variables to configure the ODIS Signer service: - -### Server - -- `NODE_ENV` - `development` or `production` -- `SERVER_PORT` - The port on which the express node app runs (8080 by default). -- `SERVER_SSL_KEY_PATH` - (Optional) Path to SSL .key file. -- `SERVER_SSL_CERT_PATH` - (Optional) Path to SSL .cert file. - -### Enabling APIs (ODIS v2) - -Each API must be explicitly enabled by setting the following env vars to true (all are false by default): - -- `PHONE_NUMBER_PRIVACY_API_ENABLED` -- `DOMAINS_API_ENABLED` - -### Database - -The service currently supports Postgres, MSSQL, and MySQL. - -- `DB_TYPE` - `postgres`, `mysql`, or `mssql` (postgres by default). -- `DB_HOST` - The URL under which your database is accessible. -- `DB_PORT` - The port on the database host (uses default for chosen DB type). -- `DB_USERNAME` - DB configuration: The DB username (postgres by default). -- `DB_PASSWORD` - DB configuration: The DB password. -- `DB_DATABASE` - DB configuration: The DB database name (phoneNumberPrivacy by default). -- `DB_USE_SSL` - DB configuration: Use SSL connection to the database (true by default). - -#### DB Migrations - -To update the signer DB schema, first run `yarn db:migrate:make ` to create a new migrations file. Then, fill in the new migration file as needed using the previous migration files as references. - -Migrations will run automatically on startup. - -### Blockchain provider - -The service needs a connection to a full node in order to access chain state. The `BLOCKCHAIN_PROVIDER` config should be a url to a node with its JSON RPC enabled. -This could be a node with RPC set up. Preferably this would be an node dedicated to this service. Alternatively, the public Forno endpoints can be used but their uptime guarantees are not as strong. For development with Alfajores, the forno url is `https://alfajores-forno.celo-testnet.org`. For Mainnet, it would be `https://forno.celo.org` - -- `BLOCKCHAIN_PROVIDER` - The blockchain node provider for chain state access. ` -- `BLOCKCHAIN_API_KEY` - Optional API key to be added to the authentication header. ` - -### Security - -The ODIS Signer service provides partial signatures that can be combined to generate domain-specific encryption keys. These keys are used for a variety of different purposes from phone number privacy to account backup encryption. It's very important to keep your BLS key share safe. We provide the following recommended best practices for keeping your key secure. - -#### Leverage a cloud keystore - -All cloud providers have a keystore offering that keeps your key secure while still being accessible by your service. ODIS Signer supports Azure, GCP, and AWS keystores. You can find configuration details in the [Keystores](#keystores) section below. - -#### Lock down your cloud - -- [ ] Ensure that you have multi-factor authentication enabled for all cloud accounts. -- [ ] Reduce access to the ODIS resources to as minimal of a set of people as possible. -- [ ] Revisit your cloud's admin set and ensure it is up to date. -- [ ] Enable Just-In-Time access policies if your cloud provider has this functionality available. For example, Azure provides [Privileged Identity Management](https://docs.microsoft.com/en-us/azure/active-directory/privileged-identity-management/pim-configure) which allows you to specify an approval list and limited time window in which an employee may access a given resource. -- [ ] Monitor/Audit access to the keystore and ODIS resource group. - -#### Create a secure backup - -The BLS key share should only exist in the keystore or as an encrypted backup. To create a backup, you can either download an encrypted copy from your keystore or manually encrypt it locally. Make sure that you keep it somewhere memorable (ex. external hard drive or password manager). Here are a couple options to create a local encrypted backup: - -- [Azure Key Vault](https://docs.microsoft.com/en-us/azure/key-vault/general/backup?tabs=azure-cli) -- [MacOS](https://support.apple.com/guide/mac-help/protect-your-mac-information-with-encryption-mh40593/mac) -- [Windows](https://support.microsoft.com/en-us/windows/how-to-encrypt-a-file-1131805c-47b8-2e3e-a705-807e13c10da7) -- [GPG Command](https://www.gnupg.org/gph/en/manual/x110.html) - -### Keystores - -Currently, the service supports Azure Key Vault (AKV), Google Secret Manager and AWS Secrets Manager. -You must specify the type, and then the keystore configs for that type as follows. - -- `KEYSTORE_TYPE` - `AzureKeyVault`, `GoogleSecretManager` or `AWSSecretManager` - -In addition, you must name your keys in your keystore according to the pattern `-` where - -- `keyName` is configurable via the env variables `PHONE_NUMBER_PRIVACY_KEY_NAME_BASE` and `DOMAINS_KEY_NAME_BASE` which default to `phoneNumberPrivacy` and `domains` respectively. -- `keyVersion` is an integer corresponding to the iteration of the given key share. The variables `PHONE_NUMBER_PRIVACY_LATEST_KEY_VERSION` and `DOMAINS_LATEST_KEY_VERSION` should specify the latest version of the appropriate key share. This version will be fetched when the signer starts up. - -For example, the first iteration of the key share used for phone number privacy should be stored as `phoneNumberPrivacy-1` and the second iteration (after resharing) should be stored as `phoneNumberPrivacy-2` unless you specify a `PHONE_NUMBER_PRIVACY_KEY_NAME_BASE` env variable, in which case `phoneNumberPrivacy` should be replaced with that value. The version numbers and `-` delimeter are mandatory and not configurable. - -**Note: if you modify the stored secrets, you must restart the signer to ensure the updated versions are used in the signer.** - -#### Azure Key Vault - -Use the following to configure the AKV connection. These values are generated when creating a service principal account (see [Configuring your Key Vault](https://www.npmjs.com/package/@azure/keyvault-keys#configuring-your-key-vault)). Or if the service is being hosted on Azure itself, authentication can be done by granted key access to the VM's managed identity, in which case the client_id, client_secret, and tenant configs can be left blank. - -- `KEYSTORE_AZURE_VAULT_NAME` - The name of your Azure Key Vault. -- `KEYSTORE_AZURE_CLIENT_ID` - (Optional) The clientId of the service principal account that has [Get, List] access to secrets. -- `KEYSTORE_AZURE_CLIENT_SECRET` - (Optional) The client secret of the same service principal account. -- `KEYSTORE_AZURE_TENANT` - (Optional) The tenant that the service principal is a member of. - -#### Google Secret Manager - -Use the following to configure the Google Secret Manager. To authenticate with Google Cloud, you can see [Setting Up Authentication](https://cloud.google.com/docs/authentication/production). By default, the google lib will use the default app credentials assigned to the host VM. If the service is being run outside of GCP, you can manually set the `GOOGLE_APPLICATION_CREDENTIALS` env var to the path to a service account json file. - -- `KEYSTORE_GOOGLE_PROJECT_ID` - The google cloud project id. - -#### AWS Secrets Manager - -Use the following to configure the AWS Secrets Manager. To authenticate with Amazon Web Services, you can see [Setting Credentials in Node.js](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html). If you are running the signer inside AWS, we do recommend to authenticate using IAM Roles. - -- `KEYSTORE_AWS_REGION` - The AWS Region code where the secret is, for example: `us-east-1`. -- `KEYSTORE_AWS_SECRET_KEY` - The key for the secret key/value pair. - -## Operations - -### Setup - -The service requires a connection to a secret store and to a SQL database. The SQL connection parameters should be configured with the `DB_*` configs stated above. Before starting the service, be sure to create a database and set the name as the value in the `DB_DATABASE` environment variable. - -#### Running locally or without docker - -To run without docker, or for development, start by git cloning the celo-monorepo. Next, run `yarn` from the monorepo root to install dependencies. - -Then start the service: `yarn start` - -#### Running in docker - -Docker images for the signer service are published to Celo's [container registry on Google Cloud](https://console.cloud.google.com/gcr/images/celo-testnet/US/celo-monorepo). Search for images with tag `phone-number-privacy-*`. Then pull the image: - -`docker pull us.gcr.io/celo-testnet/celo-monorepo:phone-number-privacy-{LATEST_TAG_HERE}` - -To start the service, run: - -`docker run -d -p 80:8080 {ENV_VARS_HERE} {IMAGE_TAG_HERE}` - -Then check on the service to make sure its running: - -`docker container ls` - -`docker logs -f {CONTAINER_ID_HERE}` - -#### Key rotations - -After a key resharing, signers should rotate their key shares as follows: - -1. Store the new key share in the keystore according to the naming convention specified in the [Keystores](#keystores) section above. -2. Increment `PHONE_NUMBER_PRIVACY_LATEST_KEY_VERSION` or `DOMAINS_LATEST_KEY_VERSION` as appropriate. This will instruct the signer to prefetch this new key version the next time it starts up, but there is no need to restart the signer at this point. -3. Notify the combiner operator that your signer is ready for the key rotation. -4. The combiner operator will run e2e tests against your signer to verify it has the correct key configuration. -5. The combiner operator will update the combiner to request the new key share version via a custom request header field once all signers are ready. -6. The signers will fetch the new key shares from their keystores upon receiving these requests. -7. When the combiner operator sees that all signers are signing with the new key share and confirms that the system is healthy, signers will be instructed to delete their old key shares. Deleting the deprecated key shares ensures they cannot be stored and used by an attacker. - -### Validate before going live - -You can test your mainnet service is set up correctly by running specific tests in the e2e suite ("[Signer configuration test]" cases) which check signatures against the public polynomial for the respective APIs. Because the tests require quota, you must first point your provider endpoint to Alfajores. - -1. Change your signer’s blockchain provider (`BLOCKCHAIN_PROVIDER`) to Alfajores Forno: `https://alfajores-forno.celo-testnet.org` -2. Navigate to the signer directory in monorepo (this directory). -3. Modify the .env file: - - - Change `ODIS_SIGNER_SERVICE_URL` to your service endpoint. - -4. Run `yarn test:signer:mainnet`. - - *Technical note: this command intentionally points the test's blockchain provider to Alfajores, in order to top up quota on Alfajores before running the test cases. It still verifies signatures against the respective mainnet polynomials.* -5. Verify that all tests pass. -6. Change your signer’s blockchain provider back to its original value (if using Forno: `https://forno.celo.org`). - -### Logs - -Error logs will be prefixed with `CELO_ODIS_ERROR_XX`. You can see a full list of them in [errors.ts](https://github.com/celo-org/celo-monorepo/blob/master/packages/phone-number-privacy/common/src/interfaces/errors.ts) in the common package. diff --git a/packages/phone-number-privacy/signer/docs/deploy-on-aws.md b/packages/phone-number-privacy/signer/docs/deploy-on-aws.md deleted file mode 100644 index c0d61e53bc..0000000000 --- a/packages/phone-number-privacy/signer/docs/deploy-on-aws.md +++ /dev/null @@ -1,115 +0,0 @@ -# Deploying to AWS - -## Prerequisites - -- awscli (https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) -- AWS credentials configured (https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) - -## Steps - -### Setting up the network and database - -The next steps will explain how to create a new VPC and a RDS Postgres database needed to run the signer. If desired, it is possible to use an existing VPC and a database deployed in some any other way. Treat it as a reference for the port/security group configuration. - -1. OPTIONAL: [Create new vpc and subnets](https://docs.aws.amazon.com/directoryservice/latest/admin-guide/gsg_create_vpc.html) for the resources (use the CIDR block and AZs that fits with your network setup). The VPC should have at least two subnets, and if it does not have any public subnet (for running the Signer container) the private subnets should have an [Internet Gateway configured](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html) - -1. Create the security groups for the database and signer: - - ```bash - aws ec2 create-security-group --description "odis database" --group-name odis-db --vpc-id - aws ec2 create-security-group --description "signer" --group-name signer --vpc-id - ``` - -1. Create the security group rules The database should expose 5432 (the port could be specified and change during the database creation), and the signer by default uses port 8080: - - ```bash - aws ec2 authorize-security-group-ingress --group-id --protocol tcp --port 5432 --source-group sg-0a1064e7d9cba38a9 - aws ec2 authorize-security-group-ingress --group-id --protocol tcp --port 8080 --cidr 0.0.0.0/0 - ``` - -1. [Create a RDS PostgreSQL DB](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_CreateDBInstance.html) (from the AWS web console). -The database does not require public access and you should accessing the VPC and security group previously created (or any other VPC and the security group if it is the case). - -For the DB Instance size, `Free tier` should be enough. - -### Running the Signer on Fargate - -ECS Fargate is a container execution service provided by AWS. It runs containers without requiring explicit management of hosts or virtual machines. -Alternatively the signer service can be run using any other service that allows to run containers, such as EC2 or EKS. In the case of EC2, you will need to install docker, configure the instance profile and follow the documentation from [the signer readme](https://github.com/celo-org/celo-monorepo/tree/master/packages/phone-number-privacy/signer). - -1. Create the service-linked role. If it is the first time you run ECS on your account you will need to run this command. - - ```bash - aws iam create-service-linked-role --aws-service-name ecs.amazonaws.com - ``` - -1. Create a Task Role for the signer ([documentation](https://docs.amazonaws.cn/en_us/AmazonECS/latest/userguide/ecs-cli-tutorial-fargate.html)). -First we will create the `assume-role` policy that allows ECS tasks to be assigned to this task role. - - ```bash - cat <<'EOF' > /tmp/task-execution-assume-role.json - { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "", - "Effect": "Allow", - "Principal": { - "Service": "ecs-tasks.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] - } - EOF - ``` - - Now we will create the task-role: - - ```bash - aws iam --region us-east-2 create-role --role-name signerTaskExecutionRole --assume-role-policy-document file:///tmp/task-execution-assume-role.json - ``` - - Finally we create the policy assigned to this task-role that allows retrieval of secrets from AWS Secret Manager. Then we attach that policy to the task role. - - ```bash - cat <<'EOF' > /tmp/secret-manager-signer-policy.json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "secretsmanager:GetResourcePolicy", - "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret", - "secretsmanager:ListSecretVersionIds" - ], - "Resource": [ - "arn:aws:secretsmanager:us-east-2:{YOUR_ACCOUNT_ID}:secret:{YOUR_SECRET_ID}" - ] - } - ] - } - EOF - - aws iam create-policy --policy-name signerTaskAllowSecretManager --policy-document file:///tmp/secret-manager-signer-policy.json - aws iam attach-role-policy --role-name signerTaskExecutionRole --policy-arn arn:aws:iam::{YOUR_ACCOUNT_ID}:policy/signerTaskAllowSecretManager - ``` - - If you want to manage the RDS postgres permissions using IAM, you can also add permissions for signer access to this policy. - -1. Create ECS Fargate cluster - - ```bash - aws ecs create-cluster --cluster-name odis --capacity-providers FARGATE_SPOT --default-capacity-provider-strategy FARGATE_SPOT - ``` - -1. Create task definition. Using the web interface, create a task definition with the next configuration: - - - [Task definition detail](./images/fargate-task-definition.png) - - [Container definition detail](./images/fargate-container-definition.png) - -1. Create the service using the task definition. - - - [Service definition detail](./images/fargate-service-definition.png) diff --git a/packages/phone-number-privacy/signer/docs/deploy-on-azure.md b/packages/phone-number-privacy/signer/docs/deploy-on-azure.md deleted file mode 100644 index 1741c5acb0..0000000000 --- a/packages/phone-number-privacy/signer/docs/deploy-on-azure.md +++ /dev/null @@ -1,3 +0,0 @@ -# Deploying to AWS - -The recommended method for deploying to Azure is to use the ARM templates and instructions specified in the [azure-templates folder](../azure-templates/README.md). \ No newline at end of file diff --git a/packages/phone-number-privacy/signer/docs/images/fargate-container-definition.png b/packages/phone-number-privacy/signer/docs/images/fargate-container-definition.png deleted file mode 100644 index 6447880501..0000000000 Binary files a/packages/phone-number-privacy/signer/docs/images/fargate-container-definition.png and /dev/null differ diff --git a/packages/phone-number-privacy/signer/docs/images/fargate-service-definition.png b/packages/phone-number-privacy/signer/docs/images/fargate-service-definition.png deleted file mode 100644 index 1d29a45188..0000000000 Binary files a/packages/phone-number-privacy/signer/docs/images/fargate-service-definition.png and /dev/null differ diff --git a/packages/phone-number-privacy/signer/docs/images/fargate-task-definition.png b/packages/phone-number-privacy/signer/docs/images/fargate-task-definition.png deleted file mode 100644 index 78021ce26d..0000000000 Binary files a/packages/phone-number-privacy/signer/docs/images/fargate-task-definition.png and /dev/null differ diff --git a/packages/phone-number-privacy/signer/index.d.ts b/packages/phone-number-privacy/signer/index.d.ts deleted file mode 100644 index 102dc17cf1..0000000000 --- a/packages/phone-number-privacy/signer/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'bunyan-debug-stream' diff --git a/packages/phone-number-privacy/signer/jest.config.js b/packages/phone-number-privacy/signer/jest.config.js deleted file mode 100644 index 1312f090e3..0000000000 --- a/packages/phone-number-privacy/signer/jest.config.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - preset: 'ts-jest', - setupFiles: ['dotenv/config'], - coverageReporters: [['lcov', { projectRoot: '../../../' }], 'text'], - collectCoverageFrom: ['./src/**'], - coverageThreshold: { - global: { - lines: 68, // TODO increase this threshold - }, - }, -} diff --git a/packages/phone-number-privacy/signer/package.json b/packages/phone-number-privacy/signer/package.json deleted file mode 100644 index 7b047e81a4..0000000000 --- a/packages/phone-number-privacy/signer/package.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "name": "@celo/phone-number-privacy-signer", - "version": "3.0.2", - "description": "Signing participator of ODIS", - "author": "Celo", - "license": "Apache-2.0", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "start": "yarn build && node -r dotenv/config dist/index.js", - "start:docker": "yarn build && node dist/index.js", - "start:docker:tracing": "yarn build && node --require ./dist/tracing.js dist/index.js", - "clean": "tsc -b . --clean", - "build": "tsc -b .", - "lint": "tslint --project .", - "test": "jest --testPathIgnorePatterns test/end-to-end", - "test:debughandles": "jest --watch --runInBand --detectOpenHandles --testPathIgnorePatterns test/end-to-end", - "test:debug": "node --inspect ../../../node_modules/.bin/jest --runInBand", - "test:coverage": "yarn test --coverage", - "test:integration": "jest --runInBand test/integration", - "test:integration:debugdb": "VERBOSE_DB_LOGGING=true jest --runInBand test/integration", - "test:e2e": "jest --runInBand test/end-to-end --testPathIgnorePatterns test/end-to-end/disabled-apis.test.ts", - "test:e2e:disabledapis": "jest --runInBand test/end-to-end/disabled-apis.test.ts", - "test:e2e:staging:0": "CONTEXT_NAME=staging ODIS_SIGNER_SERVICE_URL=https://staging-pgpnp-signer0.azurefd.net yarn test:e2e", - "test:e2e:staging:1": "CONTEXT_NAME=staging ODIS_SIGNER_SERVICE_URL=https://staging-pgpnp-signer1.azurefd.net yarn test:e2e", - "test:e2e:staging:2": "CONTEXT_NAME=staging ODIS_SIGNER_SERVICE_URL=https://staging-pgpnp-signer2.azurefd.net yarn test:e2e", - "test:e2e:alfajores:1": "CONTEXT_NAME=alfajores ODIS_SIGNER_SERVICE_URL=https://odis-alfajores-signer-1-b.azurefd.net yarn test:e2e", - "test:e2e:alfajores:2": "CONTEXT_NAME=alfajores ODIS_SIGNER_SERVICE_URL=https://odis-alfajores-signer2.azurefd.net yarn test:e2e", - "test:e2e:alfajores:3": "CONTEXT_NAME=alfajores ODIS_SIGNER_SERVICE_URL=https://odis-alfajores-signer3.azurefd.net yarn test:e2e", - "test:e2e:mainnet:brazilsouth": "CONTEXT_NAME=mainnet ODIS_SIGNER_SERVICE_URL=https://mainnet-pgpnp-brazilsouth.azurefd.net yarn test:e2e", - "test:e2e:mainnet:eastasia": "CONTEXT_NAME=mainnet ODIS_SIGNER_SERVICE_URL=https://mainnet-pgpnp-eastasia.azurefd.net yarn test:e2e", - "test:signer:mainnet": "MAINNET_ODIS_BLOCKCHAIN_PROVIDER=https://alfajores-forno.celo-testnet.org CONTEXT_NAME=mainnet yarn jest test/end-to-end -t='\\[Signer configuration test\\]'", - "db:migrate": "ts-node scripts/run-migrations.ts", - "db:migrate:make": "knex --migrations-directory ./src/common/database/migrations migrate:make -x ts", - "bls:keygen": "ts-node scripts/threshold-bls-keygen.ts", - "poprf:keygen": "ts-node scripts/poprf-keygen.ts", - "ssl:keygen": "./scripts/create-ssl-cert.sh" - }, - "dependencies": { - "@celo/base": "^5.0.4", - "bunyan": "1.8.12", - "bignumber.js": "^9.0.0", - "blind-threshold-bls": "npm:@celo/blind-threshold-bls@1.0.0-beta", - "@celo/contractkit": "^5.0.4", - "@celo/phone-number-privacy-common": "^3.0.3", - "@celo/poprf": "^0.1.9", - "@celo/utils": "^5.0.4", - "@celo/wallet-hsm-azure": "^5.0.4", - "@google-cloud/secret-manager": "3.0.0", - "@opentelemetry/api": "^1.4.1", - "@opentelemetry/auto-instrumentations-node": "^0.38.0", - "@opentelemetry/propagator-ot-trace": "^0.27.0", - "@opentelemetry/resources": "1.17.0", - "@opentelemetry/instrumentation": "^0.41.2", - "@opentelemetry/exporter-jaeger": "^1.15.2", - "@opentelemetry/sdk-metrics": "^1.15.1", - "@opentelemetry/sdk-node": "^0.41.1", - "@opentelemetry/semantic-conventions": "^1.15.1", - "@opentelemetry/sdk-trace-base": "^1.17.0", - "@opentelemetry/sdk-trace-node": "1.15.2", - "@opentelemetry/sdk-trace-web": "^1.15.1", - "@types/bunyan": "^1.8.8", - "aws-sdk": "^2.705.0", - "dotenv": "^8.2.0", - "express": "^4.17.1", - "knex": "^2.1.0", - "mssql": "^6.3.1", - "mysql2": "^2.1.0", - "cron": "^2.4.1", - "pg": "^8.2.1", - "prom-client": "12.0.0", - "promise.allsettled": "^1.0.2", - "lru-cache": "^10.0.1" - }, - "devDependencies": { - "@types/node": "18.15.13", - "@types/express": "^4.17.6", - "@types/supertest": "^2.0.12", - "sqlite3": "^5.0.8", - "supertest": "^6.2.3", - "ts-mockito": "^2.6.1", - "ts-node": "^8.3.0", - "typescript": "4.4.3" - }, - "engines": { - "node": ">=10" - } -} \ No newline at end of file diff --git a/packages/phone-number-privacy/signer/scripts/create-ssl-cert.sh b/packages/phone-number-privacy/signer/scripts/create-ssl-cert.sh deleted file mode 100755 index 5a0de974a4..0000000000 --- a/packages/phone-number-privacy/signer/scripts/create-ssl-cert.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -echo 'Creating a self-signed cert' -echo 'USE ONLY FOR DEVELOPMENT OR TESTING' - -openssl req -new -newkey rsa:4096 -days 1000 -nodes -x509 \ - -subj "/C=US/ST=CA/L=SF/O=Dis/CN=celo.org" \ - -keyout server.key -out server.cert \ No newline at end of file diff --git a/packages/phone-number-privacy/signer/scripts/poprf-keygen.ts b/packages/phone-number-privacy/signer/scripts/poprf-keygen.ts deleted file mode 100644 index 313e257f85..0000000000 --- a/packages/phone-number-privacy/signer/scripts/poprf-keygen.ts +++ /dev/null @@ -1,21 +0,0 @@ -// tslint:disable: no-console -import * as poprf from '@celo/poprf' -import crypto from 'crypto' - -const t = 2 -const n = 3 -console.log('Creating POPRF Threshold BLS keypairs with %s/%s ratio...', t, n) -console.log('USE ONLY FOR DEVELOPMENT OR TESTING') - -const seed = crypto.randomBytes(32) -const keys = poprf.thresholdKeygen(n, t, seed) -console.log('Private keys (hex):') -for (let i = 0; i < keys.numShares(); i++) { - console.log('Key #%s: %s', i + 1, Buffer.from(keys.getShare(i)).toString('hex')) -} - -console.log('Threshold Public key (base64):') -console.log(Buffer.from(keys.thresholdPublicKey).toString('base64')) - -console.log('Polynomial (hex):') -console.log(Buffer.from(keys.polynomial).toString('hex')) diff --git a/packages/phone-number-privacy/signer/scripts/run-migrations.ts b/packages/phone-number-privacy/signer/scripts/run-migrations.ts deleted file mode 100644 index d148b947bf..0000000000 --- a/packages/phone-number-privacy/signer/scripts/run-migrations.ts +++ /dev/null @@ -1,20 +0,0 @@ -// tslint:disable: no-console - -import { initDatabase } from '../src/common/database/database' -import { config } from '../src/config' - -async function start() { - console.info('Running migrations') - console.warn('It is no longer necessary to run db migrations seperately prior to startup') - await initDatabase(config, undefined) -} - -start() - .then(() => { - console.info('Migrations complete') - process.exit(0) - }) - .catch((e) => { - console.error('Migration failed', e) - process.exit(1) - }) diff --git a/packages/phone-number-privacy/signer/scripts/threshold-bls-keygen.ts b/packages/phone-number-privacy/signer/scripts/threshold-bls-keygen.ts deleted file mode 100644 index cc5172604b..0000000000 --- a/packages/phone-number-privacy/signer/scripts/threshold-bls-keygen.ts +++ /dev/null @@ -1,21 +0,0 @@ -// tslint:disable: no-console -import threshold_bls from 'blind-threshold-bls' -import crypto from 'crypto' - -const t = 2 -const n = 3 -console.log('Creating OPRF Threshold BLS keypairs with %s/%s ratio...', t, n) -console.log('USE ONLY FOR DEVELOPMENT OR TESTING') - -const seed = crypto.randomBytes(32) -const keys = threshold_bls.thresholdKeygen(n, t, seed) -console.log('Private keys (hex):') -for (let i = 0; i < keys.numShares(); i++) { - console.log('Key #%s: %s', i + 1, Buffer.from(keys.getShare(i)).toString('hex')) -} - -console.log('Threshold Public key (base64):') -console.log(Buffer.from(keys.thresholdPublicKey).toString('base64')) - -console.log('Polynomial (hex):') -console.log(Buffer.from(keys.polynomial).toString('hex')) diff --git a/packages/phone-number-privacy/signer/src/common/bls/bls-cryptography-client.ts b/packages/phone-number-privacy/signer/src/common/bls/bls-cryptography-client.ts deleted file mode 100644 index 8949df8a5c..0000000000 --- a/packages/phone-number-privacy/signer/src/common/bls/bls-cryptography-client.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ErrorMessage } from '@celo/phone-number-privacy-common' -import threshold_bls from 'blind-threshold-bls' -import Logger from 'bunyan' -import { OdisError } from '../error' -import { Counters } from '../metrics' -/* - * Computes the BLS signature for the blinded phone number. - */ -export function computeBlindedSignature( - base64BlindedMessage: string, - privateKey: string, - logger: Logger -) { - try { - const keyBuffer = Buffer.from(privateKey, 'hex') - const msgBuffer = Buffer.from(base64BlindedMessage, 'base64') - - logger.debug('Calling theshold sign') - const signedMsg = threshold_bls.partialSignBlindedMessage(keyBuffer, msgBuffer) - logger.debug('Back from threshold sign, parsing results') - - if (!signedMsg) { - throw new Error('Empty threshold sign result') - } - - return Buffer.from(signedMsg).toString('base64') - } catch (err: any) { - Counters.signatureComputationErrors.inc() - logger.error({ err }, ErrorMessage.SIGNATURE_COMPUTATION_FAILURE) - throw new OdisError(ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, err) - } -} diff --git a/packages/phone-number-privacy/signer/src/common/context.ts b/packages/phone-number-privacy/signer/src/common/context.ts deleted file mode 100644 index 16d3e4977d..0000000000 --- a/packages/phone-number-privacy/signer/src/common/context.ts +++ /dev/null @@ -1,7 +0,0 @@ -import Logger from 'bunyan' - -export interface Context { - logger: Logger - url: string - errors: string[] -} diff --git a/packages/phone-number-privacy/signer/src/common/database/database.ts b/packages/phone-number-privacy/signer/src/common/database/database.ts deleted file mode 100644 index 3af9269e75..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/database.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { rootLogger } from '@celo/phone-number-privacy-common' -import { Knex, knex } from 'knex' -import { DEV_MODE, SignerConfig, SupportedDatabase, VERBOSE_DB_LOGGING } from '../../config' - -export async function initDatabase(config: SignerConfig, migrationsPath?: string): Promise { - const logger = rootLogger(config.serviceName) - - logger.info({ config: config.db }, 'Initializing database connection') - const { type, host, port, user, password, database, ssl, poolMaxSize } = config.db - - let connection: any - let client: string - if (type === SupportedDatabase.Postgres) { - logger.info('Using Postgres') - client = 'pg' - connection = { - user, - password, - database, - host, - port: port ?? 5432, - ssl, - pool: { max: poolMaxSize }, - } - } else if (type === SupportedDatabase.MySql) { - logger.info('Using MySql') - client = 'mysql2' - connection = { - user, - password, - database, - host, - port: port ?? 3306, - ssl, - pool: { max: poolMaxSize }, - } - } else if (type === SupportedDatabase.MsSql) { - logger.info('Using MS SQL') - client = 'mssql' - connection = { - user, - password, - database, - server: host, - port: port ?? 1433, - pool: { max: poolMaxSize }, - } - } else if (type === SupportedDatabase.Sqlite) { - logger.info('Using SQLite') - client = 'sqlite3' - connection = ':memory:' - } else { - throw new Error(`Unsupported database type: ${type}`) - } - - const db = knex({ - client, - useNullAsDefault: type === SupportedDatabase.Sqlite, - connection, - debug: DEV_MODE && VERBOSE_DB_LOGGING, - }) - - logger.info('Running Migrations') - - await db.migrate.latest({ - directory: migrationsPath ?? './dist/common/database/migrations', - loadExtensions: ['.js'], - }) - - logger.info('Database initialized successfully') - return db -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20200330212224_create-accounts-table.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20200330212224_create-accounts-table.ts deleted file mode 100644 index 85ab5b88b5..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20200330212224_create-accounts-table.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Knex } from 'knex' -import { ACCOUNTS_COLUMNS } from '../models/account' - -export async function up(knex: Knex): Promise { - // This check was necessary to switch from using .ts migrations to .js migrations. - if (!(await knex.schema.hasTable('accounts'))) { - return knex.schema.createTable('accounts', (t) => { - t.string(ACCOUNTS_COLUMNS.address).notNullable().primary() - t.dateTime(ACCOUNTS_COLUMNS.createdAt).notNullable() - t.integer(ACCOUNTS_COLUMNS.numLookups).unsigned() - }) - } - return null -} - -export async function down(knex: Knex): Promise { - return knex.schema.dropTable('accounts') -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20200811163913_create_requests_table.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20200811163913_create_requests_table.ts deleted file mode 100644 index b7c92f0ecf..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20200811163913_create_requests_table.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Knex } from 'knex' -import { REQUESTS_COLUMNS } from '../models/request' - -export async function up(knex: Knex): Promise { - // This check was necessary to switch from using .ts migrations to .js migrations. - if (!(await knex.schema.hasTable('requests'))) { - return knex.schema.createTable('requests', (t) => { - t.string(REQUESTS_COLUMNS.address).notNullable() - t.dateTime(REQUESTS_COLUMNS.timestamp).notNullable() - t.string(REQUESTS_COLUMNS.blindedQuery).notNullable() - t.primary([ - REQUESTS_COLUMNS.address, - REQUESTS_COLUMNS.timestamp, - REQUESTS_COLUMNS.blindedQuery, - ]) - }) - } - return null -} - -export async function down(knex: Knex): Promise { - return knex.schema.dropTable('requests') -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20210421212301_create-indices.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20210421212301_create-indices.ts deleted file mode 100644 index 9b01ae66ae..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20210421212301_create-indices.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Knex } from 'knex' -import { ACCOUNTS_COLUMNS } from '../models/account' - -export async function up(knex: Knex): Promise { - if (!(await knex.schema.hasTable('accounts'))) { - throw new Error('Unexpected error: Could not find accounts') - } - return knex.schema.alterTable('accounts', (t) => { - t.index(ACCOUNTS_COLUMNS.address) - }) -} - -export async function down(knex: Knex): Promise { - return knex.schema.alterTable('accounts', (t) => { - t.dropIndex(ACCOUNTS_COLUMNS.address) - }) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20210921173354_create-domain-state.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20210921173354_create-domain-state.ts deleted file mode 100644 index cb8c7406e2..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20210921173354_create-domain-state.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Knex } from 'knex' -import { DOMAIN_STATE_COLUMNS } from '../models/domain-state' - -export async function up(knex: Knex): Promise { - // Due to a name change, the old migration uses the old names of these tables - // The change-name-domain-state migration then updates the name - // to match the value stored in DOMAIN_STATE_TABLE - if (!(await knex.schema.hasTable('domainsStates'))) { - return knex.schema.createTable('domainsStates', (t) => { - t.string(DOMAIN_STATE_COLUMNS.domainHash).notNullable().primary() - t.integer(DOMAIN_STATE_COLUMNS.counter).nullable() - t.boolean(DOMAIN_STATE_COLUMNS.disabled).notNullable().defaultTo(false) - t.integer(DOMAIN_STATE_COLUMNS.timer).nullable() - }) - } - - return null -} - -export async function down(knex: Knex): Promise { - return knex.schema.dropTable('domainsStates') -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20220119165335_domain-requests.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20220119165335_domain-requests.ts deleted file mode 100644 index d5873ba080..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20220119165335_domain-requests.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Knex } from 'knex' -import { DOMAIN_REQUESTS_COLUMNS, DOMAIN_REQUESTS_TABLE } from '../models/domain-request' - -export async function up(knex: Knex): Promise { - if (!(await knex.schema.hasTable(DOMAIN_REQUESTS_TABLE))) { - return knex.schema.createTable(DOMAIN_REQUESTS_TABLE, (t) => { - t.string(DOMAIN_REQUESTS_COLUMNS.domainHash).notNullable() - // Note: this field was nullable in an older version of the migration. - // More context is included as part of this issue: - // https://github.com/celo-org/celo-monorepo/issues/9909 - t.dateTime(DOMAIN_REQUESTS_COLUMNS.timestamp).notNullable() - t.string(DOMAIN_REQUESTS_COLUMNS.blindedMessage).notNullable() - t.primary([ - DOMAIN_REQUESTS_COLUMNS.domainHash, - DOMAIN_REQUESTS_COLUMNS.timestamp, - DOMAIN_REQUESTS_COLUMNS.blindedMessage, - ]) - }) - } - - return null -} - -export async function down(knex: Knex): Promise { - return knex.schema.dropTable(DOMAIN_REQUESTS_TABLE) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20220923161710_pnp-requests-onchain.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20220923161710_pnp-requests-onchain.ts deleted file mode 100644 index 8f5563912a..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20220923161710_pnp-requests-onchain.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Knex } from 'knex' -import { REQUESTS_COLUMNS, REQUESTS_TABLE } from '../models/request' - -export async function up(knex: Knex): Promise { - if (!(await knex.schema.hasTable(REQUESTS_TABLE))) { - return knex.schema.createTable(REQUESTS_TABLE, (t) => { - t.string(REQUESTS_COLUMNS.address).notNullable() - t.dateTime(REQUESTS_COLUMNS.timestamp).notNullable() - t.string(REQUESTS_COLUMNS.blindedQuery).notNullable() - t.primary([ - REQUESTS_COLUMNS.address, - // Note: the order of these should be switched. Done in follow up migration. - REQUESTS_COLUMNS.timestamp, - REQUESTS_COLUMNS.blindedQuery, - ]) - }) - } - return null -} - -export async function down(knex: Knex): Promise { - return knex.schema.dropTable(REQUESTS_TABLE) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20220923165433_pnp-accounts-onchain.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20220923165433_pnp-accounts-onchain.ts deleted file mode 100644 index a4bb390eb7..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20220923165433_pnp-accounts-onchain.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Knex } from 'knex' -import { ACCOUNTS_COLUMNS, ACCOUNTS_TABLE } from '../models/account' - -export async function up(knex: Knex): Promise { - // This check was necessary to switch from using .ts migrations to .js migrations. - if (!(await knex.schema.hasTable(ACCOUNTS_TABLE))) { - return knex.schema.createTable(ACCOUNTS_TABLE, (t) => { - // Note: this creates a double index and may be hurting insertion times. Fixed in follow up migration. - // (https://www.percona.com/blog/duplicate-indexes-and-redundant-indexes/) - t.string(ACCOUNTS_COLUMNS.address).notNullable().primary().index() - t.dateTime(ACCOUNTS_COLUMNS.createdAt).notNullable() - t.integer(ACCOUNTS_COLUMNS.numLookups).unsigned() - }) - } - return null -} - -export async function down(knex: Knex): Promise { - return knex.schema.dropTable(ACCOUNTS_TABLE) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20221102141044_change-name-domain-state.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20221102141044_change-name-domain-state.ts deleted file mode 100644 index d1515a40f3..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20221102141044_change-name-domain-state.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Knex } from 'knex' -import { DOMAIN_STATE_TABLE } from '../models/domain-state' - -// The original create-domain-state migration used a different name for DOMAIN_STATE_TABLE -export async function up(knex: Knex): Promise { - return knex.schema.renameTable('domainsStates', DOMAIN_STATE_TABLE) -} - -export async function down(knex: Knex): Promise { - return knex.schema.renameTable(DOMAIN_STATE_TABLE, 'domainsStates') -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20221213125526_add-constraint-domain-requests.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20221213125526_add-constraint-domain-requests.ts deleted file mode 100644 index b86ff3c083..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20221213125526_add-constraint-domain-requests.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Knex } from 'knex' -import { DOMAIN_REQUESTS_COLUMNS, DOMAIN_REQUESTS_TABLE } from '../models/domain-request' - -// The goal of this migration is to ensure that the timestamp column is -// not nullable, to make sure that all signers' DB states end up in the same place -// despite the required change in the old migration from nullable -> nonNullable -// for the timestamp column (due to errors in MySQL). -// Revisit all of this when returning to: -// https://github.com/celo-org/celo-monorepo/issues/9909 -export async function up(knex: Knex): Promise { - return knex.schema.alterTable(DOMAIN_REQUESTS_TABLE, (t) => { - t.dropNullable(DOMAIN_REQUESTS_COLUMNS.timestamp) - }) -} - -export async function down(knex: Knex): Promise { - return knex.schema.alterTable(DOMAIN_REQUESTS_TABLE, (t) => { - t.setNullable(DOMAIN_REQUESTS_COLUMNS.timestamp) - }) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223141_rename-legacy-accounts-table.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223141_rename-legacy-accounts-table.ts deleted file mode 100644 index 876e12aebc..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223141_rename-legacy-accounts-table.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Knex } from 'knex' - -export async function up(knex: Knex): Promise { - return knex.schema.renameTable('accounts', 'accountsLegacy') -} - -export async function down(knex: Knex): Promise { - return knex.schema.renameTable('accountsLegacy', 'accounts') -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223301_rename-legacy-requests-table.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223301_rename-legacy-requests-table.ts deleted file mode 100644 index fc9fda86a2..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223301_rename-legacy-requests-table.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Knex } from 'knex' - -export async function up(knex: Knex): Promise { - return knex.schema.renameTable('requests', 'requestsLegacy') -} - -export async function down(knex: Knex): Promise { - return knex.schema.renameTable('requestsLegacy', 'requests') -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223359_drop-legacy-requests-table.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223359_drop-legacy-requests-table.ts deleted file mode 100644 index 64d2c70f1f..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223359_drop-legacy-requests-table.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Knex } from 'knex' -import { REQUESTS_COLUMNS } from '../models/request' - -export async function up(knex: Knex): Promise { - return knex.schema.dropTable('requestsLegacy') -} - -export async function down(knex: Knex): Promise { - // Note this will not restore data - return knex.schema.createTable('requestsLegacy', (t) => { - t.string(REQUESTS_COLUMNS.address).notNullable() - t.dateTime(REQUESTS_COLUMNS.timestamp).notNullable() - t.string(REQUESTS_COLUMNS.blindedQuery).notNullable() - t.primary([REQUESTS_COLUMNS.address, REQUESTS_COLUMNS.timestamp, REQUESTS_COLUMNS.blindedQuery]) - }) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223416_drop-legacy-accounts-table.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223416_drop-legacy-accounts-table.ts deleted file mode 100644 index 5e320111c7..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223416_drop-legacy-accounts-table.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Knex } from 'knex' -import { ACCOUNTS_COLUMNS } from '../models/account' - -export async function up(knex: Knex): Promise { - return knex.schema.dropTable('accountsLegacy') -} - -export async function down(knex: Knex): Promise { - // Note this will not restore data - return knex.schema.createTable('accountsLegacy', (t) => { - t.string(ACCOUNTS_COLUMNS.address).notNullable().primary() - t.dateTime(ACCOUNTS_COLUMNS.createdAt).notNullable() - t.integer(ACCOUNTS_COLUMNS.numLookups).unsigned() - }) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818224022_drop-timestamp-from-requests-primary-key.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818224022_drop-timestamp-from-requests-primary-key.ts deleted file mode 100644 index 78eae451e5..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818224022_drop-timestamp-from-requests-primary-key.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Knex } from 'knex' -import { REQUESTS_COLUMNS, REQUESTS_TABLE } from '../models/request' - -export async function up(knex: Knex): Promise { - return knex.schema.alterTable(REQUESTS_TABLE, (t) => { - t.dropPrimary() - t.primary([REQUESTS_COLUMNS.address, REQUESTS_COLUMNS.blindedQuery, REQUESTS_COLUMNS.timestamp]) - }) -} - -export async function down(knex: Knex): Promise { - return knex.schema.alterTable(REQUESTS_TABLE, (t) => { - t.dropPrimary() - t.primary([REQUESTS_COLUMNS.address, REQUESTS_COLUMNS.timestamp, REQUESTS_COLUMNS.blindedQuery]) - }) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818230722_drop-redundant-account-index.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818230722_drop-redundant-account-index.ts deleted file mode 100644 index 182137a7c8..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818230722_drop-redundant-account-index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Knex } from 'knex' -import { ACCOUNTS_COLUMNS, ACCOUNTS_TABLE } from '../models/account' - -export async function up(knex: Knex): Promise { - return knex.schema.alterTable(ACCOUNTS_TABLE, (t) => { - t.dropIndex(ACCOUNTS_COLUMNS.address) - }) -} - -export async function down(knex: Knex): Promise { - return knex.schema.alterTable(ACCOUNTS_TABLE, (t) => { - t.index(ACCOUNTS_COLUMNS.address) - }) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230825150243_add_signature_request_column.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230825150243_add_signature_request_column.ts deleted file mode 100644 index 5f7c415728..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20230825150243_add_signature_request_column.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Knex } from 'knex' -import { REQUESTS_COLUMNS, REQUESTS_TABLE } from '../models/request' - -export async function up(knex: Knex): Promise { - return knex.schema.alterTable(REQUESTS_TABLE, (t) => { - t.string(REQUESTS_COLUMNS.signature) - }) -} - -export async function down(knex: Knex): Promise { - return knex.schema.alterTable(REQUESTS_TABLE, (t) => { - t.dropColumn(REQUESTS_COLUMNS.signature) - }) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230828180024_add-request-timestamp-index.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230828180024_add-request-timestamp-index.ts deleted file mode 100644 index 9801fa3fba..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20230828180024_add-request-timestamp-index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Knex } from 'knex' -import { REQUESTS_COLUMNS, REQUESTS_TABLE } from '../models/request' - -export async function up(knex: Knex): Promise { - return knex.schema.alterTable(REQUESTS_TABLE, (t) => { - t.index(REQUESTS_COLUMNS.timestamp) - }) -} - -export async function down(knex: Knex): Promise { - return knex.schema.alterTable(REQUESTS_TABLE, (t) => { - t.dropIndex(REQUESTS_COLUMNS.timestamp) - }) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/models/account.ts b/packages/phone-number-privacy/signer/src/common/database/models/account.ts deleted file mode 100644 index 3b74dc5b52..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/models/account.ts +++ /dev/null @@ -1,21 +0,0 @@ -export const ACCOUNTS_TABLE = 'accountsOnChain' - -export enum ACCOUNTS_COLUMNS { - address = 'address', - createdAt = 'created_at', - numLookups = 'num_lookups', -} - -export interface AccountRecord { - [ACCOUNTS_COLUMNS.address]: string - [ACCOUNTS_COLUMNS.createdAt]: Date - [ACCOUNTS_COLUMNS.numLookups]: number -} - -export function toAccountRecord(account: string, numLookups: number): AccountRecord { - return { - [ACCOUNTS_COLUMNS.address]: account, - [ACCOUNTS_COLUMNS.createdAt]: new Date(), - [ACCOUNTS_COLUMNS.numLookups]: numLookups, - } -} diff --git a/packages/phone-number-privacy/signer/src/common/database/models/domain-request.ts b/packages/phone-number-privacy/signer/src/common/database/models/domain-request.ts deleted file mode 100644 index 84a3a6fd95..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/models/domain-request.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Domain, domainHash } from '@celo/phone-number-privacy-common' - -export const DOMAIN_REQUESTS_TABLE = 'domainRequests' -export enum DOMAIN_REQUESTS_COLUMNS { - domainHash = 'domainHash', - timestamp = 'timestamp', - blindedMessage = 'blinded_message', -} - -export interface DomainRequestRecord { - [DOMAIN_REQUESTS_COLUMNS.domainHash]: string - [DOMAIN_REQUESTS_COLUMNS.timestamp]: Date - [DOMAIN_REQUESTS_COLUMNS.blindedMessage]: string -} - -export function toDomainRequestRecord( - domain: D, - blindedMessage: string -): DomainRequestRecord { - return { - [DOMAIN_REQUESTS_COLUMNS.domainHash]: domainHash(domain).toString('hex'), - [DOMAIN_REQUESTS_COLUMNS.timestamp]: new Date(), - [DOMAIN_REQUESTS_COLUMNS.blindedMessage]: blindedMessage, - } -} diff --git a/packages/phone-number-privacy/signer/src/common/database/models/domain-state.ts b/packages/phone-number-privacy/signer/src/common/database/models/domain-state.ts deleted file mode 100644 index ae1a964ece..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/models/domain-state.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { - Domain, - domainHash, - DomainState, - SequentialDelayDomainState, -} from '@celo/phone-number-privacy-common/lib/domains' - -export const DOMAIN_STATE_TABLE = 'domainState' -export enum DOMAIN_STATE_COLUMNS { - domainHash = 'domainHash', - counter = 'counter', - timer = 'timer', - disabled = 'disabled', -} - -export interface DomainStateRecord { - [DOMAIN_STATE_COLUMNS.domainHash]: string - [DOMAIN_STATE_COLUMNS.disabled]: boolean - [DOMAIN_STATE_COLUMNS.counter]: number - [DOMAIN_STATE_COLUMNS.timer]: number -} - -export function toDomainStateRecord( - domain: D, - domainState: DomainState -): DomainStateRecord { - return { - [DOMAIN_STATE_COLUMNS.domainHash]: domainHash(domain).toString('hex'), - [DOMAIN_STATE_COLUMNS.disabled]: domainState.disabled, - [DOMAIN_STATE_COLUMNS.counter]: domainState.counter, - [DOMAIN_STATE_COLUMNS.timer]: domainState.timer, - } -} - -export function toSequentialDelayDomainState( - record: DomainStateRecord, - attemptTime?: number -): SequentialDelayDomainState { - return { - disabled: record[DOMAIN_STATE_COLUMNS.disabled], - counter: record[DOMAIN_STATE_COLUMNS.counter], - timer: record[DOMAIN_STATE_COLUMNS.timer], - // Timestamp precision is lowered to seconds to reduce the chance of effective timing attacks. - now: attemptTime ?? Math.floor(Date.now() / 1000), - } -} diff --git a/packages/phone-number-privacy/signer/src/common/database/models/request.ts b/packages/phone-number-privacy/signer/src/common/database/models/request.ts deleted file mode 100644 index 2cc9aba498..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/models/request.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const REQUESTS_TABLE = 'requestsOnChain' - -export enum REQUESTS_COLUMNS { - address = 'caller_address', - timestamp = 'timestamp', - blindedQuery = 'blinded_query', - signature = 'signature', -} - -export interface PnpSignRequestRecord { - [REQUESTS_COLUMNS.address]: string - [REQUESTS_COLUMNS.timestamp]: Date - [REQUESTS_COLUMNS.blindedQuery]: string - [REQUESTS_COLUMNS.signature]: string | undefined -} - -export function toPnpSignRequestRecord( - account: string, - blindedQuery: string, - signature: string -): PnpSignRequestRecord { - return { - [REQUESTS_COLUMNS.address]: account, - [REQUESTS_COLUMNS.timestamp]: new Date(), - [REQUESTS_COLUMNS.blindedQuery]: blindedQuery, - [REQUESTS_COLUMNS.signature]: signature, - } -} diff --git a/packages/phone-number-privacy/signer/src/common/database/utils.ts b/packages/phone-number-privacy/signer/src/common/database/utils.ts deleted file mode 100644 index 70f7ade523..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/utils.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ErrorMessage } from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { OdisError } from '../error' -import { Counters, Histograms, Labels, newMeter } from '../metrics' - -export type DatabaseErrorMessage = - | ErrorMessage.DATABASE_GET_FAILURE - | ErrorMessage.DATABASE_INSERT_FAILURE - | ErrorMessage.DATABASE_UPDATE_FAILURE - | ErrorMessage.DATABASE_REMOVE_FAILURE - -export function countAndThrowDBError( - err: any, - logger: Logger, - errorMsg: DatabaseErrorMessage -): never { - let label: Labels - switch (errorMsg) { - case ErrorMessage.DATABASE_UPDATE_FAILURE: - label = Labels.UPDATE - break - case ErrorMessage.DATABASE_GET_FAILURE: - label = Labels.READ - break - case ErrorMessage.DATABASE_INSERT_FAILURE: - label = Labels.INSERT - break - case ErrorMessage.DATABASE_REMOVE_FAILURE: - label = Labels.BATCH_DELETE - break - default: - throw new Error('Unknown database label provided') - } - - Counters.databaseErrors.labels(label).inc() - logger.error({ err }, errorMsg) - throw new OdisError(errorMsg) -} - -export function doMeteredSql( - sqlLabel: string, - errorMsg: DatabaseErrorMessage, - logger: Logger, - fn: () => Promise -): Promise { - const meter = newMeter(Histograms.dbOpsInstrumentation, sqlLabel) - - return meter(async () => { - const res = await fn() - return res - }).catch((err) => countAndThrowDBError(err, logger, errorMsg)) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/wrappers/account.ts b/packages/phone-number-privacy/signer/src/common/database/wrappers/account.ts deleted file mode 100644 index 264f894669..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/wrappers/account.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { ErrorMessage } from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Knex } from 'knex' -import { config } from '../../../config' -import { AccountRecord, ACCOUNTS_COLUMNS, ACCOUNTS_TABLE, toAccountRecord } from '../models/account' -import { doMeteredSql } from '../utils' - -/* - * Returns how many queries the account has already performed. - */ -export async function getPerformedQueryCount( - db: Knex, - account: string, - logger: Logger -): Promise { - logger.debug({ account }, 'Getting performed query count') - return doMeteredSql( - 'getPerformedQueryCount', - ErrorMessage.DATABASE_GET_FAILURE, - logger, - async () => { - const queryCounts = await db(ACCOUNTS_TABLE) - .where(ACCOUNTS_COLUMNS.address, account) - .select(ACCOUNTS_COLUMNS.numLookups) - .first() - .timeout(config.db.timeout) - return queryCounts === undefined ? 0 : queryCounts[ACCOUNTS_COLUMNS.numLookups] - } - ) -} - -async function getAccountExists( - db: Knex, - account: string, - logger: Logger, - trx?: Knex.Transaction -): Promise { - return doMeteredSql('getAccountExists', ErrorMessage.DATABASE_GET_FAILURE, logger, async () => { - const sql = db(ACCOUNTS_TABLE) - .where(ACCOUNTS_COLUMNS.address, account) - .first() - .timeout(config.db.timeout) - - const accountRecord = await (trx != null ? sql.transacting(trx) : sql) - return !!accountRecord - }) -} - -/* - * Increments query count in database. If record doesn't exist, create one. - */ -export async function incrementQueryCount( - db: Knex, - account: string, - logger: Logger, - trx?: Knex.Transaction -): Promise { - logger.debug({ account }, 'Incrementing query count') - return doMeteredSql( - 'incrementQueryCount', - ErrorMessage.DATABASE_INSERT_FAILURE, - logger, - async () => { - if (await getAccountExists(db, account, logger, trx)) { - const sql = db(ACCOUNTS_TABLE) - .where(ACCOUNTS_COLUMNS.address, account) - .increment(ACCOUNTS_COLUMNS.numLookups, 1) - .timeout(config.db.timeout) - await (trx != null ? sql.transacting(trx) : sql) - } else { - const sql = db(ACCOUNTS_TABLE) - .insert(toAccountRecord(account, 1)) - .timeout(config.db.timeout) - await (trx != null ? sql.transacting(trx) : sql) - } - } - ) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-request.ts b/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-request.ts deleted file mode 100644 index ef7506d75f..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-request.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Domain, domainHash, ErrorMessage } from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Knex } from 'knex' -import { config } from '../../../config' -import { - DOMAIN_REQUESTS_COLUMNS, - DOMAIN_REQUESTS_TABLE, - DomainRequestRecord, - toDomainRequestRecord, -} from '../models/domain-request' -import { doMeteredSql } from '../utils' - -// TODO implement replay handling; this file is currently unused -// https://github.com/celo-org/celo-monorepo/issues/9909 - -export async function getDomainRequestRecordExists( - db: Knex, - domain: D, - blindedMessage: string, - trx: Knex.Transaction, - logger: Logger -): Promise { - const hash = domainHash(domain).toString('hex') - logger.debug({ domain, blindedMessage, hash }, 'Checking if domain request exists') - return doMeteredSql( - 'getDomainRequestRecordExists', - ErrorMessage.DATABASE_GET_FAILURE, - logger, - async () => { - const existingRequest = await db(DOMAIN_REQUESTS_TABLE) - .transacting(trx) - .where({ - [DOMAIN_REQUESTS_COLUMNS.domainHash]: hash, - [DOMAIN_REQUESTS_COLUMNS.blindedMessage]: blindedMessage, - }) - .first() - .timeout(config.db.timeout) - return !!existingRequest - } - ) -} - -export async function storeDomainRequestRecord( - db: Knex, - domain: D, - blindedMessage: string, - trx: Knex.Transaction, - logger: Logger -) { - logger.debug({ domain, blindedMessage }, 'Storing domain restricted signature request') - return doMeteredSql( - 'storeDomainRequestRecord', - ErrorMessage.DATABASE_INSERT_FAILURE, - logger, - async () => { - await db(DOMAIN_REQUESTS_TABLE) - .transacting(trx) - .insert(toDomainRequestRecord(domain, blindedMessage)) - .timeout(config.db.timeout) - } - ) -} - -export async function deleteDomainRequestsOlderThan( - db: Knex, - date: Date, - logger: Logger, - trx?: Knex.Transaction -): Promise { - logger.debug(`Removing request older than: ${date}`) - if (date > new Date()) { - logger.debug('Date is in the future') - return 0 - } - return doMeteredSql( - 'deleteDomainRequestsOlderThan', - ErrorMessage.DATABASE_REMOVE_FAILURE, - logger, - async () => { - const sql = db(DOMAIN_REQUESTS_TABLE) - .where(DOMAIN_REQUESTS_COLUMNS.timestamp, '<=', date) - .del() - return trx != null ? sql.transacting(trx) : sql - } - ) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-state.ts b/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-state.ts deleted file mode 100644 index 8cebb8625b..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-state.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { ErrorMessage } from '@celo/phone-number-privacy-common' -import { Domain, domainHash } from '@celo/phone-number-privacy-common/lib/domains' -import Logger from 'bunyan' -import { Knex } from 'knex' -import { config } from '../../../config' -import { - DOMAIN_STATE_COLUMNS, - DOMAIN_STATE_TABLE, - DomainStateRecord, - toDomainStateRecord, -} from '../models/domain-state' -import { doMeteredSql } from '../utils' - -export async function setDomainDisabled( - db: Knex, - domain: D, - trx: Knex.Transaction, - logger: Logger -): Promise { - const hash = domainHash(domain).toString('hex') - logger.debug({ hash, domain }, 'Disabling domain') - return doMeteredSql('disableDomain', ErrorMessage.DATABASE_UPDATE_FAILURE, logger, async () => { - await db(DOMAIN_STATE_TABLE) - .transacting(trx) - .where(DOMAIN_STATE_COLUMNS.domainHash, hash) - .update(DOMAIN_STATE_COLUMNS.disabled, true) - .timeout(config.db.timeout) - }) -} - -export async function getDomainStateRecordOrEmpty( - db: Knex, - domain: Domain, - logger: Logger, - trx?: Knex.Transaction -): Promise { - return ( - (await getDomainStateRecord(db, domain, logger, trx)) ?? createEmptyDomainStateRecord(domain) - ) -} - -export function createEmptyDomainStateRecord(domain: Domain, disabled: boolean = false) { - return toDomainStateRecord(domain, { - timer: 0, - counter: 0, - disabled, - now: 0, - }) -} - -export async function getDomainStateRecord( - db: Knex, - domain: D, - logger: Logger, - trx?: Knex.Transaction -): Promise { - const hash = domainHash(domain).toString('hex') - logger.debug({ hash, domain }, 'Getting domain state from db') - return doMeteredSql( - 'getDomainStateRecord', - ErrorMessage.DATABASE_GET_FAILURE, - logger, - async () => { - const sql = db(DOMAIN_STATE_TABLE) - .where(DOMAIN_STATE_COLUMNS.domainHash, hash) - .first() - .timeout(config.db.timeout) - - const result = await (trx != null ? sql.transacting(trx) : sql) - // bools are stored in db as ints (1 or 0), so we must cast them back - if (result) { - result.disabled = !!result.disabled - } - - return result ?? null - } - ) -} - -export async function updateDomainStateRecord( - db: Knex, - domain: D, - domainState: DomainStateRecord, - trx: Knex.Transaction, - logger: Logger -): Promise { - const hash = domainHash(domain).toString('hex') - logger.debug({ hash, domain, domainState }, 'Update domain state') - return doMeteredSql( - 'updateDomainStateRecord', - ErrorMessage.DATABASE_UPDATE_FAILURE, - logger, - async () => { - // Check whether the domain is already in the database. - // The current signature flow results in redundant queries of the domain state. - // Consider optimizing in the future: https://github.com/celo-org/celo-monorepo/issues/9855 - const result = await getDomainStateRecord(db, domain, logger, trx) - - // Insert or update the domain state record. - if (!result) { - await insertDomainStateRecord(db, domainState, trx, logger) - } else { - await db(DOMAIN_STATE_TABLE) - .transacting(trx) - .where(DOMAIN_STATE_COLUMNS.domainHash, hash) - .update(domainState) - .timeout(config.db.timeout) - } - } - ) -} - -export async function insertDomainStateRecord( - db: Knex, - domainState: DomainStateRecord, - trx: Knex.Transaction, - logger: Logger -): Promise { - logger.debug({ domainState }, 'Insert domain state') - return doMeteredSql( - 'insertDomainState', - ErrorMessage.DATABASE_INSERT_FAILURE, - logger, - async () => { - await db(DOMAIN_STATE_TABLE) - .transacting(trx) - .insert(domainState) - .timeout(config.db.timeout) - return domainState - } - ) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/wrappers/request.ts b/packages/phone-number-privacy/signer/src/common/database/wrappers/request.ts deleted file mode 100644 index 9e843fa934..0000000000 --- a/packages/phone-number-privacy/signer/src/common/database/wrappers/request.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { ErrorMessage } from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Knex } from 'knex' -import { config } from '../../../config' -import { - PnpSignRequestRecord, - REQUESTS_COLUMNS, - REQUESTS_TABLE, - toPnpSignRequestRecord, -} from '../models/request' -import { doMeteredSql } from '../utils' - -export async function getRequestIfExists( - db: Knex, - account: string, - blindedQuery: string, - logger: Logger -): Promise { - logger.debug(`Checking if request exists for account: ${account}, blindedQuery: ${blindedQuery}`) - return doMeteredSql('getRequestIfExists', ErrorMessage.DATABASE_GET_FAILURE, logger, async () => { - const existingRequest = await db(REQUESTS_TABLE) - .where({ - [REQUESTS_COLUMNS.address]: account, - [REQUESTS_COLUMNS.blindedQuery]: blindedQuery, - }) - .first() - .timeout(config.db.timeout) - return existingRequest - }) -} - -export async function insertRequest( - db: Knex, - account: string, - blindedQuery: string, - signature: string, - logger: Logger, - trx?: Knex.Transaction -): Promise { - logger.debug( - `Storing salt request for: ${account}, blindedQuery: ${blindedQuery}, signature: ${signature}` - ) - return doMeteredSql('insertRequest', ErrorMessage.DATABASE_INSERT_FAILURE, logger, async () => { - const sql = db(REQUESTS_TABLE) - .insert(toPnpSignRequestRecord(account, blindedQuery, signature)) - .timeout(config.db.timeout) - await (trx != null ? sql.transacting(trx) : sql) - }) -} - -export async function deleteRequestsOlderThan( - db: Knex, - since: Date, - logger: Logger -): Promise { - logger.debug(`Removing request older than: ${since}`) - if (since > new Date(Date.now())) { - logger.debug('Date is in the future') - return 0 - } - return doMeteredSql( - 'deleteRequestsOlderThan', - ErrorMessage.DATABASE_REMOVE_FAILURE, - logger, - async () => { - const sql = db(REQUESTS_TABLE) - .where(REQUESTS_COLUMNS.timestamp, '<=', since) - .del() - return sql - } - ) -} diff --git a/packages/phone-number-privacy/signer/src/common/error.ts b/packages/phone-number-privacy/signer/src/common/error.ts deleted file mode 100644 index 91060f2369..0000000000 --- a/packages/phone-number-privacy/signer/src/common/error.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ErrorType } from '@celo/phone-number-privacy-common' - -export class OdisError extends Error { - constructor(readonly code: ErrorType, readonly parent?: Error, readonly status: number = 500) { - // This is necessary when extending Error Classes - super(code) // 'Error' breaks prototype chain here - Object.setPrototypeOf(this, new.target.prototype) // restore prototype chain - } -} - -export function wrapError( - valueOrError: Promise, - code: ErrorType, - status: number = 500 -): Promise { - return valueOrError.catch((parentErr) => { - throw new OdisError(code, parentErr, status) - }) -} diff --git a/packages/phone-number-privacy/signer/src/common/handler.ts b/packages/phone-number-privacy/signer/src/common/handler.ts deleted file mode 100644 index 4efad1e381..0000000000 --- a/packages/phone-number-privacy/signer/src/common/handler.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { - ErrorMessage, - ErrorType, - OdisRequest, - OdisResponse, - PnpQuotaStatus, - send, - SequentialDelayDomainState, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import opentelemetry, { SpanStatusCode } from '@opentelemetry/api' -import { SemanticAttributes } from '@opentelemetry/semantic-conventions' -import Logger from 'bunyan' -import { Request, Response } from 'express' -import * as client from 'prom-client' -import { getSignerVersion } from '../config' -import { OdisError } from './error' -import { Counters, newMeter } from './metrics' - -const tracer = opentelemetry.trace.getTracer('signer-tracer') - -export interface Locals { - logger: Logger -} - -export type PromiseHandler = ( - request: Request<{}, {}, R>, - res: Response, Locals> -) => Promise - -export function catchErrorHandler( - handler: PromiseHandler -): PromiseHandler { - return async (req, res) => { - try { - Counters.requests.labels(req.url).inc() - await handler(req, res) - } catch (err: any) { - // Handle any errors that otherwise managed to escape the proper handlers - const logger = res.locals.logger - logger.error(ErrorMessage.CAUGHT_ERROR_IN_ENDPOINT_HANDLER) - logger.error(err) - Counters.errorsCaughtInEndpointHandler.inc() // TODO investigate why this gets triggered on full node errors - - if (!res.headersSent) { - if (err instanceof OdisError) { - sendFailure(err.code, err.status, res, req.url) - } else { - sendFailure(ErrorMessage.UNKNOWN_ERROR, 500, res, req.url) - } - } else { - // Getting to this error likely indicates that an inner handler - // does not terminate after sending a response, and then throws an error. - logger.error(ErrorMessage.ERROR_AFTER_RESPONSE_SENT) - Counters.errorsThrownAfterResponseSent.inc() - } - } - } -} - -export function tracingHandler( - handler: PromiseHandler -): PromiseHandler { - return async (req, res) => { - return tracer.startActiveSpan( - req.url, - { - attributes: { - [SemanticAttributes.HTTP_ROUTE]: req.path, - [SemanticAttributes.HTTP_METHOD]: req.method, - [SemanticAttributes.HTTP_CLIENT_IP]: req.ip, - }, - }, - async (span) => { - try { - await handler(req, res) - span.setStatus({ - code: SpanStatusCode.OK, - }) - } catch (err: any) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: err instanceof Error ? err.message : 'Fail', - }) - throw err - } finally { - span.end() - } - } - ) - } -} - -export function meteringHandler( - histogram: client.Histogram, - handler: PromiseHandler -): PromiseHandler { - return (req, res) => newMeter(histogram, req.url)(async () => handler(req, res)) -} - -export function timeoutHandler( - timeoutMs: number, - handler: PromiseHandler -): PromiseHandler { - return async (req, res) => { - const timeoutId = setTimeout(() => { - if (!res.headersSent) { - Counters.timeouts.inc() - sendFailure(ErrorMessage.TIMEOUT_FROM_SIGNER, 500, res, req.url) - } - }, timeoutMs) - - try { - await handler(req, res) - } finally { - // Clears the timeout if it answers first - clearTimeout(timeoutId) - } - } -} - -export function connectionClosedHandler( - handler: PromiseHandler -): PromiseHandler { - return async (req, res) => { - req.on('close', () => { - if (res.socket?.destroyed) { - res.locals.logger.info('connection closed') - Counters.connectionClosed.inc() - res.end() - } - }) - - await handler(req, res) - } -} - -export async function disabledHandler( - req: Request<{}, {}, R>, - response: Response, Locals> -): Promise { - sendFailure(WarningMessage.API_UNAVAILABLE, 503, response, req.url) -} - -export interface Result { - status: number - body: OdisResponse -} - -export type ResultHandler = ( - request: Request<{}, {}, R>, - res: Response, Locals> -) => Promise> - -export function resultHandler( - resHandler: ResultHandler -): PromiseHandler { - return async (req, res) => { - const result = await resHandler(req, res) - // Check if the response was ended - if (!res.writableEnded) { - send(res, result.body, result.status, res.locals.logger) - Counters.responses.labels(req.url, result.status.toString()).inc() - } - } -} - -export function errorResult( - status: number, - error: string, - quotaStatus?: PnpQuotaStatus | { status: SequentialDelayDomainState } -): Result { - // TODO remove any - return { - status, - body: { - success: false, - version: getSignerVersion(), - error, - ...quotaStatus, - }, - } -} - -function sendFailure( - error: ErrorType, - status: number, - response: Response, - endpoint: string, - body?: Record // TODO remove any -) { - // Check if the response was ended - if (!response.writableEnded) { - send( - response, - { - success: false, - version: getSignerVersion(), - error, - ...body, - }, - status, - response.locals.logger - ) - Counters.responses.labels(endpoint, status.toString()).inc() - } -} diff --git a/packages/phone-number-privacy/signer/src/common/key-management/aws-key-provider.ts b/packages/phone-number-privacy/signer/src/common/key-management/aws-key-provider.ts deleted file mode 100644 index 338af55b8c..0000000000 --- a/packages/phone-number-privacy/signer/src/common/key-management/aws-key-provider.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { ErrorMessage, rootLogger } from '@celo/phone-number-privacy-common' -import { SecretsManager } from 'aws-sdk' -import { config } from '../../config' -import { Key, KeyProviderBase } from './key-provider-base' - -interface SecretStringResult { - [key: string]: string -} - -export class AWSKeyProvider extends KeyProviderBase { - public async fetchPrivateKeyFromStore(key: Key) { - const logger = rootLogger(config.serviceName) - try { - // Credentials are managed by AWS client as described in https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html - const { region, secretKey } = config.keystore.aws - const client = new SecretsManager({ region }) - client.config.update({ region }) - - const customKeyVersionString = this.getCustomKeyVersionString(key) - logger.debug(`Attempting to fetch key named: ${customKeyVersionString}`) - const privateKey = await this.fetch(client, customKeyVersionString, secretKey) - this.setPrivateKey(key, privateKey) - } catch (err) { - logger.info('Error retrieving key') - logger.error(err) - throw new Error(ErrorMessage.KEY_FETCH_ERROR) - } - } - - private async fetch(client: SecretsManager, secretName: string, secretKey: string) { - // check for empty strings from undefined env vars - if (!secretName) { - throw new Error('key name is undefined') - } - - const response = await client.getSecretValue({ SecretId: secretName }).promise() - - let privateKey - if (response.SecretString) { - privateKey = this.tryParseSecretString(response.SecretString, secretKey) - } else if (response.SecretBinary) { - // @ts-ignore AWS sdk typings not quite correct - const buff = Buffer.from(response.SecretBinary, 'base64') - privateKey = buff.toString('ascii') - } else { - throw new Error('Response has neither string nor binary') - } - - if (!privateKey) { - throw new Error('Secret is empty or undefined') - } - - return privateKey - } - - private tryParseSecretString(secretString: string, key: string) { - if (!secretString) { - throw new Error('Cannot parse empty string') - } - if (!key) { - throw new Error('Cannot parse secret without key') - } - - try { - const secret = JSON.parse(secretString) as SecretStringResult - return secret[key] - } catch (e) { - throw new Error('Expecting JSON, secret string is not valid JSON') - } - } -} diff --git a/packages/phone-number-privacy/signer/src/common/key-management/azure-key-provider.ts b/packages/phone-number-privacy/signer/src/common/key-management/azure-key-provider.ts deleted file mode 100644 index d6c5a67581..0000000000 --- a/packages/phone-number-privacy/signer/src/common/key-management/azure-key-provider.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ErrorMessage, rootLogger } from '@celo/phone-number-privacy-common' -import { AzureKeyVaultClient } from '@celo/wallet-hsm-azure' -import { config } from '../../config' -import { Key, KeyProviderBase } from './key-provider-base' - -export class AzureKeyProvider extends KeyProviderBase { - public async fetchPrivateKeyFromStore(key: Key) { - const logger = rootLogger(config.serviceName) - try { - const { vaultName } = config.keystore.azure - const client = new AzureKeyVaultClient(vaultName) - - const customKeyVersionString = this.getCustomKeyVersionString(key) - logger.debug(`Attempting to fetch key named: ${customKeyVersionString}`) - const privateKey = await this.fetch(client, customKeyVersionString) - this.setPrivateKey(key, privateKey) - } catch (err) { - logger.info('Error retrieving key') - logger.error(err) - throw new Error(ErrorMessage.KEY_FETCH_ERROR) - } - } - - private async fetch(client: AzureKeyVaultClient, secretName: string) { - // check for empty strings from undefined env vars - if (!secretName) { - throw new Error('key name is undefined') - } - - const privateKey = await client.getSecret(secretName) - - if (!privateKey) { - throw new Error('Key is empty or undefined') - } - - return privateKey - } -} diff --git a/packages/phone-number-privacy/signer/src/common/key-management/google-key-provider.ts b/packages/phone-number-privacy/signer/src/common/key-management/google-key-provider.ts deleted file mode 100644 index 0d3c7f9fa0..0000000000 --- a/packages/phone-number-privacy/signer/src/common/key-management/google-key-provider.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { ErrorMessage, rootLogger } from '@celo/phone-number-privacy-common' -import { SecretManagerServiceClient } from '@google-cloud/secret-manager/build/src/v1' -import { config } from '../../config' -import { Key, KeyProviderBase } from './key-provider-base' - -export class GoogleKeyProvider extends KeyProviderBase { - public async fetchPrivateKeyFromStore(key: Key) { - const logger = rootLogger(config.serviceName) - try { - const { projectId } = config.keystore.google - const client = new SecretManagerServiceClient() - - const customKeyName = this.getCustomKeyName(key) - const keyVersion = key.version.toString() - logger.debug(`Attempting to fetch key named: ${customKeyName}, version: ${keyVersion}`) - const privateKey = await this.fetch(client, projectId, customKeyName, keyVersion) - this.setPrivateKey(key, privateKey) - } catch (err) { - logger.info('Error retrieving key') - logger.error(err) - throw new Error(ErrorMessage.KEY_FETCH_ERROR) - } - } - - private async fetch( - client: SecretManagerServiceClient, - projectId: string, - secretName: string, - secretVersion: string - ) { - // check for empty strings from undefined env vars - if (!(projectId && secretName && secretVersion)) { - throw new Error('key name is undefined') - } - const secretID = `projects/${projectId}/secrets/${secretName}/versions/${secretVersion}` - const [versionResponse] = await client.accessSecretVersion({ name: secretID }) - - // Extract the payload as a string. - const privateKey = versionResponse?.payload?.data?.toString() - - if (!privateKey) { - throw new Error('Key is empty or undefined') - } - - return privateKey - } -} diff --git a/packages/phone-number-privacy/signer/src/common/key-management/key-provider-base.ts b/packages/phone-number-privacy/signer/src/common/key-management/key-provider-base.ts deleted file mode 100644 index db1f8e03fc..0000000000 --- a/packages/phone-number-privacy/signer/src/common/key-management/key-provider-base.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { config } from '../../config' - -export enum DefaultKeyName { - PHONE_NUMBER_PRIVACY = 'phoneNumberPrivacy', - DOMAINS = 'domains', -} -export interface Key { - name: DefaultKeyName - version: number -} -export interface KeyProvider { - fetchPrivateKeyFromStore: (key: Key) => Promise - getPrivateKey: (key: Key) => string - getPrivateKeyOrFetchFromStore: (key: Key) => Promise -} - -const PRIVATE_KEY_SIZE = 72 - -export abstract class KeyProviderBase implements KeyProvider { - protected privateKeys: Map - - constructor() { - this.privateKeys = new Map() - } - - public getPrivateKey(key: Key) { - const privateKey = this.privateKeys.get(this.getCustomKeyVersionString(key)) - if (!privateKey) { - throw new Error(`Private key is unavailable: ${key}`) - } - return privateKey - } - - public async getPrivateKeyOrFetchFromStore(key: Key): Promise { - if (key.version < 0) { - throw new Error('Invalid private key version. Key version must be a positive integer.') - } - try { - return this.getPrivateKey(key) - } catch { - await this.fetchPrivateKeyFromStore(key) - return this.getPrivateKey(key) - } - } - - public abstract fetchPrivateKeyFromStore(key: Key): Promise - - getCustomKeyVersionString(key: Key): string { - return `${this.getCustomKeyName(key)}-${key.version}` - } - - protected setPrivateKey(key: Key, privateKey: string) { - privateKey = privateKey ? privateKey.trim() : '' - if (privateKey.length !== PRIVATE_KEY_SIZE) { - throw new Error('Invalid private key') - } - this.privateKeys.set(this.getCustomKeyVersionString(key), privateKey) - } - - protected getCustomKeyName(key: Key) { - switch (key.name) { - case DefaultKeyName.PHONE_NUMBER_PRIVACY: - return config.keystore.keys.phoneNumberPrivacy.name || key.name - case DefaultKeyName.DOMAINS: - return config.keystore.keys.domains.name || key.name - default: - return key.name - } - } -} diff --git a/packages/phone-number-privacy/signer/src/common/key-management/key-provider.ts b/packages/phone-number-privacy/signer/src/common/key-management/key-provider.ts deleted file mode 100644 index 6e6e9a384d..0000000000 --- a/packages/phone-number-privacy/signer/src/common/key-management/key-provider.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { rootLogger } from '@celo/phone-number-privacy-common' -import { SignerConfig, SupportedKeystore } from '../../config' -import { AWSKeyProvider } from './aws-key-provider' -import { AzureKeyProvider } from './azure-key-provider' -import { GoogleKeyProvider } from './google-key-provider' -import { DefaultKeyName, Key, KeyProvider } from './key-provider-base' -import { MockKeyProvider } from './mock-key-provider' - -export function keysToPrefetch(config: SignerConfig): Key[] { - return [ - { - name: DefaultKeyName.PHONE_NUMBER_PRIVACY, - version: config.keystore.keys.phoneNumberPrivacy.latest, - }, - { - name: DefaultKeyName.DOMAINS, - version: config.keystore.keys.domains.latest, - }, - ] -} - -export async function initKeyProvider(config: SignerConfig): Promise { - const logger = rootLogger(config.serviceName) - logger.info('Initializing keystore') - const type = config.keystore.type - - let keyProvider: KeyProvider - - if (type === SupportedKeystore.AZURE_KEY_VAULT) { - logger.info('Using Azure key vault') - keyProvider = new AzureKeyProvider() - } else if (type === SupportedKeystore.GOOGLE_SECRET_MANAGER) { - logger.info('Using Google Secret Manager') - keyProvider = new GoogleKeyProvider() - } else if (type === SupportedKeystore.AWS_SECRET_MANAGER) { - logger.info('Using AWS Secret Manager') - keyProvider = new AWSKeyProvider() - } else if (type === SupportedKeystore.MOCK_SECRET_MANAGER) { - logger.info('Using Mock Secret Manager') - keyProvider = new MockKeyProvider() - } else { - throw new Error('Valid keystore type must be provided') - } - - logger.info(`Fetching keys: ${JSON.stringify(keysToPrefetch(config))}`) - await Promise.all( - keysToPrefetch(config).map(keyProvider.fetchPrivateKeyFromStore.bind(keyProvider)) - ) - logger.info('Done fetching key. Key provider initialized successfully.') - - return keyProvider -} diff --git a/packages/phone-number-privacy/signer/src/common/key-management/mock-key-provider.ts b/packages/phone-number-privacy/signer/src/common/key-management/mock-key-provider.ts deleted file mode 100644 index 8d3e11b5b2..0000000000 --- a/packages/phone-number-privacy/signer/src/common/key-management/mock-key-provider.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V1, - DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V2, - DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V3, - PNP_THRESHOLD_DEV_PK_SHARE_1_V1, - PNP_THRESHOLD_DEV_PK_SHARE_1_V2, - PNP_THRESHOLD_DEV_PK_SHARE_1_V3, -} from '@celo/phone-number-privacy-common/lib/test/values' -import { DefaultKeyName, Key, KeyProviderBase } from './key-provider-base' - -export class MockKeyProvider extends KeyProviderBase { - // prettier-ignore - constructor( - private keyMocks: Map = new Map([ - [ - `${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, - PNP_THRESHOLD_DEV_PK_SHARE_1_V1 - ], - [ - `${DefaultKeyName.PHONE_NUMBER_PRIVACY}-2`, - PNP_THRESHOLD_DEV_PK_SHARE_1_V2, - ], - [ - `${DefaultKeyName.PHONE_NUMBER_PRIVACY}-3`, - PNP_THRESHOLD_DEV_PK_SHARE_1_V3 - ], - [ - `${DefaultKeyName.DOMAINS}-1`, - DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V1 - ], - [ - `${DefaultKeyName.DOMAINS}-2`, - DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V2, - ], - [ - `${DefaultKeyName.DOMAINS}-3`, - DOMAINS_THRESHOLD_DEV_PK_SHARE_1_V3 - ], - ]) - ) { - super() - } - - public async fetchPrivateKeyFromStore(key: Key) { - const keyString = this.keyMocks.get(this.getCustomKeyVersionString(key)) - if (keyString) { - return this.setPrivateKey(key, keyString) - } - throw new Error('unknown key for MockKeyProvider') - } -} diff --git a/packages/phone-number-privacy/signer/src/common/metrics.ts b/packages/phone-number-privacy/signer/src/common/metrics.ts deleted file mode 100644 index 311509ede6..0000000000 --- a/packages/phone-number-privacy/signer/src/common/metrics.ts +++ /dev/null @@ -1,108 +0,0 @@ -import * as client from 'prom-client' - -const { Counter, Histogram } = client - -client.collectDefaultMetrics() - -// This is just so autocomplete will remind devs what the options are. -export enum Labels { - READ = 'read', - UPDATE = 'update', - INSERT = 'insert', - BATCH_DELETE = 'batch-delete', -} - -export const Counters = { - requests: new Counter({ - name: 'requests', - help: 'Counter for the number of requests received', - labelNames: ['endpoint'], - }), - responses: new Counter({ - name: 'responses', - help: 'Counter for the number of responses sent', - labelNames: ['endpoint', 'statusCode'], - }), - databaseErrors: new Counter({ - name: 'database_errors', - help: 'Counter for the number of database errors', - labelNames: ['type'], - }), - blockchainErrors: new Counter({ - name: 'blockchain_errors', - help: 'Counter for the number of errors from interacting with the blockchain', - }), - signatureComputationErrors: new Counter({ - name: 'signature_computation_errors', - help: 'Counter for the number of signature computation errors', - }), - duplicateRequests: new Counter({ - name: 'duplicate_requests', - help: 'Counter for the number of duplicate signature requests received', - }), - requestsWithWalletAddress: new Counter({ - name: 'requests_with_wallet_address', - help: 'Counter for the number of requests in which WALLET_KEY authentication is used', - }), - testQuotaBypassedRequests: new Counter({ - name: 'test_quota_bypassed_requests', - help: 'Counter for the number of requests not requiring quota (testing only)', - }), - timeouts: new Counter({ - name: 'timeouts', - help: 'Counter for the number of signer timeouts as measured by the signer', - }), - errorsCaughtInEndpointHandler: new Counter({ - name: 'errors_caught_in_endpoint_handler', - help: 'Counter for the number of errors caught in the outermost endpoint handler', - }), - errorsThrownAfterResponseSent: new Counter({ - name: 'errors_thrown_after_response_sent', - help: 'Counter for the number of errors thrown after a response was already sent', - }), - connectionClosed: new Counter({ - name: 'connection_closed', - help: 'Counter for the number of closed connections caught in Signer', - }), -} -const buckets = [ - 0.001, 0.01, 0.1, 0.2, 0.3, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.2, 1.4, 1.6, 1.8, 2, 2.3, 2.6, 2.9, 3.5, - 4, 5, 10, -] - -export const Histograms = { - responseLatency: new Histogram({ - name: 'signature_endpoint_latency', - help: 'Histogram tracking latency of signature endpoint', - labelNames: ['endpoint'], - buckets, - }), - fullNodeLatency: new Histogram({ - name: 'full_node_latency', - help: 'Histogram tracking latency of full node requests', - labelNames: ['codeSegment'], - buckets, - }), - dbOpsInstrumentation: new Histogram({ - name: 'db_ops_instrumentation', - help: 'Histogram tracking latency of all database operations', - labelNames: ['operation'], - buckets, - }), - userRemainingQuotaAtRequest: new Histogram({ - name: 'user_remaining_quota_at_request', - help: 'Histogram tracking remaining quota of users at time of request', - labelNames: ['endpoint'], - buckets, - }), -} - -export function newMeter( - histogram: client.Histogram, - ...labels: string[] -): (fn: () => Promise) => Promise { - return (fn) => { - const _meter = histogram.labels(...labels).startTimer() - return fn().finally(_meter) - } -} diff --git a/packages/phone-number-privacy/signer/src/common/quota.ts b/packages/phone-number-privacy/signer/src/common/quota.ts deleted file mode 100644 index dca689e646..0000000000 --- a/packages/phone-number-privacy/signer/src/common/quota.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - DomainQuotaStatusRequest, - DomainRestrictedSignatureRequest, - OdisRequest, - PnpQuotaRequest, - PnpQuotaStatus, - SignMessageRequest, -} from '@celo/phone-number-privacy-common' -import { DomainStateRecord } from './database/models/domain-state' - -// prettier-ignore -export type OdisQuotaStatus = R extends - | DomainQuotaStatusRequest | DomainRestrictedSignatureRequest ? DomainStateRecord : never - | R extends SignMessageRequest | PnpQuotaRequest ? PnpQuotaStatus: never - -// TODO this is only used in Domain endpoints now -export interface OdisQuotaStatusResult { - sufficient: boolean - state: OdisQuotaStatus -} diff --git a/packages/phone-number-privacy/signer/src/common/tracing-utils.ts b/packages/phone-number-privacy/signer/src/common/tracing-utils.ts deleted file mode 100644 index ddd4091790..0000000000 --- a/packages/phone-number-privacy/signer/src/common/tracing-utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -import opentelemetry, { SpanStatusCode } from '@opentelemetry/api' - -const tracer = opentelemetry.trace.getTracer('signer-tracer') - -export function traceAsyncFunction(traceName: string, fn: () => Promise): Promise { - return tracer.startActiveSpan(traceName, async (span) => { - try { - const res = await fn() - span.setStatus({ code: SpanStatusCode.OK }) - return res - } catch (err: any) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: err instanceof Error ? err.message : undefined, - }) - throw err - } finally { - span.end() - } - }) -} diff --git a/packages/phone-number-privacy/signer/src/common/web3/contracts.ts b/packages/phone-number-privacy/signer/src/common/web3/contracts.ts deleted file mode 100644 index 31fc58755b..0000000000 --- a/packages/phone-number-privacy/signer/src/common/web3/contracts.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { retryAsyncWithBackOffAndTimeout } from '@celo/base' -import { ContractKit } from '@celo/contractkit' -import { getDataEncryptionKey } from '@celo/phone-number-privacy-common' -import { BigNumber } from 'bignumber.js' -import Logger from 'bunyan' -import { config } from '../../config' -import { Counters, Histograms, newMeter } from '../metrics' - -export async function getOnChainOdisPayments( - kit: ContractKit, - logger: Logger, - account: string -): Promise { - const _meter = newMeter(Histograms.fullNodeLatency, 'getOnChainOdisPayments') - return _meter(() => - retryAsyncWithBackOffAndTimeout( - async () => (await kit.contracts.getOdisPayments()).totalPaidCUSD(account), - config.fullNodeRetryCount, - [], - config.fullNodeRetryDelayMs, - undefined, - config.fullNodeTimeoutMs - ).catch((err: any) => { - logger.error({ err, account }, 'failed to get on-chain odis balance for account') - Counters.blockchainErrors.inc() - throw err - }) - ) -} - -export async function getDEK(kit: ContractKit, logger: Logger, account: string): Promise { - const _meter = newMeter(Histograms.fullNodeLatency, 'getDataEncryptionKey') - return _meter(() => - getDataEncryptionKey( - account, - kit, - logger, - config.fullNodeTimeoutMs, - config.fullNodeRetryCount, - config.fullNodeRetryDelayMs - ).catch((err) => { - logger.error({ err, account }, 'failed to get on-chain DEK for account') - Counters.blockchainErrors.inc() - throw err - }) - ) -} diff --git a/packages/phone-number-privacy/signer/src/config.ts b/packages/phone-number-privacy/signer/src/config.ts deleted file mode 100644 index 0c886cdd5f..0000000000 --- a/packages/phone-number-privacy/signer/src/config.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { - BlockchainConfig, - DB_POOL_MAX_SIZE, - DB_TIMEOUT, - FULL_NODE_TIMEOUT_IN_MS, - RETRY_COUNT, - RETRY_DELAY_IN_MS, - toBool, -} from '@celo/phone-number-privacy-common' -import BigNumber from 'bignumber.js' - -require('dotenv').config() - -export function getSignerVersion(): string { - return process.env.npm_package_version ?? '0.0.0' -} -export const DEV_MODE = process.env.NODE_ENV !== 'production' -export const VERBOSE_DB_LOGGING = toBool(process.env.VERBOSE_DB_LOGGING, false) - -export enum SupportedDatabase { - Postgres = 'postgres', // PostgresSQL - MySql = 'mysql', // MySQL - MsSql = 'mssql', // Microsoft SQL Server - Sqlite = 'sqlite3', // SQLite (for testing) -} - -export enum SupportedKeystore { - AZURE_KEY_VAULT = 'AzureKeyVault', - GOOGLE_SECRET_MANAGER = 'GoogleSecretManager', - AWS_SECRET_MANAGER = 'AWSSecretManager', - MOCK_SECRET_MANAGER = 'MockSecretManager', -} - -export interface SignerConfig { - serviceName: string - server: { - port: string | number | undefined - sslKeyPath?: string - sslCertPath?: string - } - quota: { - unverifiedQueryMax: number - additionalVerifiedQueryMax: number - queryPerTransaction: number - minDollarBalance: BigNumber - minEuroBalance: BigNumber - minCeloBalance: BigNumber - queryPriceInCUSD: BigNumber - } - api: { - domains: { - enabled: boolean - } - phoneNumberPrivacy: { - enabled: boolean - } - } - blockchain: BlockchainConfig - db: { - type: SupportedDatabase - user: string - password: string - database: string - host: string - port?: number - ssl: boolean - poolMaxSize: number - timeout: number - } - keystore: { - type: SupportedKeystore - keys: { - phoneNumberPrivacy: { - name: string - latest: number - } - domains: { - name: string - latest: number - } - } - azure: { - clientID: string - clientSecret: string - tenant: string - vaultName: string - } - google: { - projectId: string - } - aws: { - region: string - secretKey: string - } - } - timeout: number - test_quota_bypass_percentage: number - fullNodeTimeoutMs: number - fullNodeRetryCount: number - fullNodeRetryDelayMs: number - shouldMockAccountService: boolean - mockDek: string - mockTotalQuota: number - shouldMockRequestService: boolean - requestPrunningDays: number - requestPrunningAtServerStart: boolean - requestPrunningJobCronPattern: string -} - -const env = process.env as any -export const config: SignerConfig = { - serviceName: env.SERVICE_NAME ?? 'odis-signer', - server: { - port: Number(env.SERVER_PORT ?? 8080), - sslKeyPath: env.SERVER_SSL_KEY_PATH, - sslCertPath: env.SERVER_SSL_CERT_PATH, - }, - quota: { - unverifiedQueryMax: Number(env.UNVERIFIED_QUERY_MAX ?? 10), - additionalVerifiedQueryMax: Number(env.ADDITIONAL_VERIFIED_QUERY_MAX ?? 30), - queryPerTransaction: Number(env.QUERY_PER_TRANSACTION ?? 2), - // Min balance is .01 cUSD - minDollarBalance: new BigNumber(env.MIN_DOLLAR_BALANCE ?? 1e16), - // Min balance is .01 cEUR - minEuroBalance: new BigNumber(env.MIN_DOLLAR_BALANCE ?? 1e16), - // Min balance is .005 CELO - minCeloBalance: new BigNumber(env.MIN_DOLLAR_BALANCE ?? 5e15), - // Equivalent to 0.001 cUSD/query - queryPriceInCUSD: new BigNumber(env.QUERY_PRICE_PER_CUSD ?? 0.001), - }, - api: { - domains: { - enabled: toBool(env.DOMAINS_API_ENABLED, false), - }, - phoneNumberPrivacy: { - enabled: toBool(env.PHONE_NUMBER_PRIVACY_API_ENABLED, false), - }, - }, - blockchain: { - provider: env.BLOCKCHAIN_PROVIDER, - apiKey: env.BLOCKCHAIN_API_KEY, - }, - db: { - type: env.DB_TYPE ? env.DB_TYPE.toLowerCase() : SupportedDatabase.Postgres, - user: env.DB_USERNAME, - password: env.DB_PASSWORD, - database: env.DB_DATABASE, - host: env.DB_HOST, - port: env.DB_PORT ? Number(env.DB_PORT) : undefined, - ssl: toBool(env.DB_USE_SSL, true), - poolMaxSize: Number(env.DB_POOL_MAX_SIZE ?? DB_POOL_MAX_SIZE), - timeout: Number(env.DB_TIMEOUT ?? DB_TIMEOUT), - }, - keystore: { - type: env.KEYSTORE_TYPE, - keys: { - phoneNumberPrivacy: { - name: env.PHONE_NUMBER_PRIVACY_KEY_NAME_BASE, - latest: Number(env.PHONE_NUMBER_PRIVACY_LATEST_KEY_VERSION ?? 1), - }, - domains: { - name: env.DOMAINS_KEY_NAME_BASE, - latest: Number(env.DOMAINS_LATEST_KEY_VERSION ?? 1), - }, - }, - azure: { - clientID: env.KEYSTORE_AZURE_CLIENT_ID, - clientSecret: env.KEYSTORE_AZURE_CLIENT_SECRET, - tenant: env.KEYSTORE_AZURE_TENANT, - vaultName: env.KEYSTORE_AZURE_VAULT_NAME, - }, - google: { - projectId: env.KEYSTORE_GOOGLE_PROJECT_ID, - }, - aws: { - region: env.KEYSTORE_AWS_REGION, - secretKey: env.KEYSTORE_AWS_SECRET_KEY, - }, - }, - timeout: Number(env.ODIS_SIGNER_TIMEOUT ?? 5000), - test_quota_bypass_percentage: Number(env.TEST_QUOTA_BYPASS_PERCENTAGE ?? 0), - fullNodeTimeoutMs: Number(env.TIMEOUT_MS ?? FULL_NODE_TIMEOUT_IN_MS), - fullNodeRetryCount: Number(env.RETRY_COUNT ?? RETRY_COUNT), - fullNodeRetryDelayMs: Number(env.RETRY_DELAY_IN_MS ?? RETRY_DELAY_IN_MS), - shouldMockAccountService: toBool(env.SHOULD_MOCK_ACCOUNT_SERVICE, false), - mockDek: env.MOCK_DEK, - mockTotalQuota: Number(env.MOCK_TOTAL_QUOTA ?? 10), - shouldMockRequestService: toBool(env.SHOULD_MOCK_REQUEST_SERVICE, false), - requestPrunningDays: Number(env.REQUEST_PRUNNING_DAYS ?? 7), - requestPrunningAtServerStart: toBool(env.REQUEST_PRUNNING_AT_SERVER_START, false), - requestPrunningJobCronPattern: env.REQUEST_PRUNNING_JOB_CRON_PATTERN ?? '0 0 3 * * *', -} diff --git a/packages/phone-number-privacy/signer/src/domain/endpoints/disable/action.ts b/packages/phone-number-privacy/signer/src/domain/endpoints/disable/action.ts deleted file mode 100644 index 1cf71fb0d1..0000000000 --- a/packages/phone-number-privacy/signer/src/domain/endpoints/disable/action.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { - DisableDomainRequest, - disableDomainRequestSchema, - domainHash, - DomainSchema, - verifyDisableDomainRequestAuthenticity, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { Request } from 'express' -import { Knex } from 'knex' -import { toSequentialDelayDomainState } from '../../../common/database/models/domain-state' -import { - createEmptyDomainStateRecord, - getDomainStateRecord, - insertDomainStateRecord, - setDomainDisabled, -} from '../../../common/database/wrappers/domain-state' -import { errorResult, ResultHandler } from '../../../common/handler' -import { getSignerVersion } from '../../../config' - -export function domainDisable(db: Knex): ResultHandler { - return async (request, response) => { - const { logger } = response.locals - - if (!isValidRequest(request)) { - return errorResult(400, WarningMessage.INVALID_INPUT) - } - if (!verifyDisableDomainRequestAuthenticity(request.body)) { - return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) - } - - const { domain } = request.body - - logger.info( - { - name: domain.name, - version: domain.version, - hash: domainHash(domain).toString('hex'), - }, - 'Processing request to disable domain' - ) - - const res = await db.transaction(async (trx) => { - const domainStateRecord = - (await getDomainStateRecord(db, domain, logger, trx)) ?? - (await insertDomainStateRecord(db, createEmptyDomainStateRecord(domain, true), trx, logger)) - if (!domainStateRecord.disabled) { - await setDomainDisabled(db, domain, trx, logger) - domainStateRecord.disabled = true - } - return { - // TODO revisit this - success: true, - status: 200, - domainStateRecord, - } - // Note: we previously timed out inside the trx to ensure timeouts roll back DB trx - // return timeout(disableDomainHandler, [], this.config.timeout, timeoutError) - }) - - return { - status: res.status, - body: { - success: true, - version: getSignerVersion(), - status: toSequentialDelayDomainState(res.domainStateRecord), - }, - } - } -} - -function isValidRequest( - request: Request<{}, {}, unknown> -): request is Request<{}, {}, DisableDomainRequest> { - return disableDomainRequestSchema(DomainSchema).is(request.body) -} diff --git a/packages/phone-number-privacy/signer/src/domain/endpoints/quota/action.ts b/packages/phone-number-privacy/signer/src/domain/endpoints/quota/action.ts deleted file mode 100644 index e397b2a050..0000000000 --- a/packages/phone-number-privacy/signer/src/domain/endpoints/quota/action.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { - domainHash, - DomainQuotaStatusRequest, - domainQuotaStatusRequestSchema, - DomainSchema, - verifyDomainQuotaStatusRequestAuthenticity, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { Request } from 'express' -import { toSequentialDelayDomainState } from '../../../common/database/models/domain-state' -import { errorResult, ResultHandler } from '../../../common/handler' -import { getSignerVersion } from '../../../config' -import { DomainQuotaService } from '../../services/quota' - -export function domainQuota(quota: DomainQuotaService): ResultHandler { - return async (request, response) => { - const { logger } = response.locals - - if (!isValidRequest(request)) { - return errorResult(400, WarningMessage.INVALID_INPUT) - } - if (!verifyDomainQuotaStatusRequestAuthenticity(request.body)) { - return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) - } - - const { domain } = request.body - - logger.info('Processing request to get domain quota status', { - name: domain.name, - version: domain.version, - hash: domainHash(domain).toString('hex'), - }) - const domainStateRecord = await quota.getQuotaStatus(domain, logger) - - return { - status: 200, - body: { - success: true, - version: getSignerVersion(), - status: toSequentialDelayDomainState(domainStateRecord), - }, - } - } -} - -function isValidRequest( - request: Request<{}, {}, unknown> -): request is Request<{}, {}, DomainQuotaStatusRequest> { - return domainQuotaStatusRequestSchema(DomainSchema).is(request.body) -} diff --git a/packages/phone-number-privacy/signer/src/domain/endpoints/sign/action.ts b/packages/phone-number-privacy/signer/src/domain/endpoints/sign/action.ts deleted file mode 100644 index 7cc0ec853c..0000000000 --- a/packages/phone-number-privacy/signer/src/domain/endpoints/sign/action.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { - Domain, - domainHash, - DomainRestrictedSignatureRequest, - domainRestrictedSignatureRequestSchema, - DomainSchema, - ErrorType, - getRequestKeyVersion, - KEY_VERSION_HEADER, - requestHasValidKeyVersion, - ThresholdPoprfServer, - verifyDomainRestrictedSignatureRequestAuthenticity, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { EIP712Optional } from '@celo/utils/lib/sign-typed-data-utils' -import Logger from 'bunyan' -import { Request } from 'express' -import { Knex } from 'knex' -import { - DomainStateRecord, - toSequentialDelayDomainState, -} from '../../../common/database/models/domain-state' -import { errorResult, ResultHandler } from '../../../common/handler' -import { DefaultKeyName, Key, KeyProvider } from '../../../common/key-management/key-provider-base' -import { OdisQuotaStatusResult } from '../../../common/quota' -import { getSignerVersion, SignerConfig } from '../../../config' -import { DomainQuotaService } from '../../services/quota' - -type TrxResult = - | { - success: false - status: number - domainStateRecord: DomainStateRecord - error: ErrorType - } - | { - success: true - status: number - domainStateRecord: DomainStateRecord - key: Key - signature: string - } - -export function domainSign( - db: Knex, - config: SignerConfig, - quota: DomainQuotaService, - keyProvider: KeyProvider -): ResultHandler { - return async (request, response) => { - const { logger } = response.locals - - if (!isValidRequest(request)) { - return errorResult(400, WarningMessage.INVALID_INPUT) - } - if (!requestHasValidKeyVersion(request, logger)) { - return errorResult(400, WarningMessage.INVALID_KEY_VERSION_REQUEST) - } - if (!verifyDomainRestrictedSignatureRequestAuthenticity(request.body)) { - return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) - } - - const { domain } = request.body - - logger.info( - { - name: domain.name, - version: domain.version, - hash: domainHash(domain).toString('hex'), - }, - 'Processing request to get domain signature ' - ) - const res: TrxResult = await db.transaction(async (trx) => { - // Get the current domain state record, or use an empty record if one does not exist. - const domainStateRecord: DomainStateRecord = await quota.getQuotaStatus(domain, logger, trx) - - // Note that this action occurs in the same transaction as the remainder of the siging - // action. As a result, this is included here rather than in the authentication function. - if (!nonceCheck(domainStateRecord, request.body, logger)) { - return { - // TODO revisit this - success: false, - status: 401, - domainStateRecord, - error: WarningMessage.INVALID_NONCE, - } - } - - const quotaStatus: OdisQuotaStatusResult = - await quota.checkAndUpdateQuotaStatus( - // TODO types - domainStateRecord, - request.body.domain, - trx, - logger - ) - - if (!quotaStatus.sufficient) { - logger.warn( - { - name: domain.name, - version: domain.version, - hash: domainHash(domain), - }, - `Exceeded quota` - ) - return { - success: false, - status: 429, - domainStateRecord: quotaStatus.state, - error: WarningMessage.EXCEEDED_QUOTA, - } - } - - const key: Key = { - version: getRequestKeyVersion(request, logger) ?? config.keystore.keys.domains.latest, - name: DefaultKeyName.DOMAINS, - } - - // Compute evaluation inside transaction so it will rollback on error. - const evaluation: Buffer = await sign( - domain, - request.body.blindedMessage, - key, - logger, - keyProvider - ) - - return { - success: true, - status: 200, - domainStateRecord: quotaStatus.state, - key, - signature: evaluation.toString('base64'), - } - }) - - if (res.success) { - response.set(KEY_VERSION_HEADER, res.key.version.toString()) - return { - status: 200, - body: { - success: true, - version: getSignerVersion(), - signature: res.signature, - status: toSequentialDelayDomainState(res.domainStateRecord), - }, - } - } else { - return errorResult(res.status, res.error, { - status: toSequentialDelayDomainState(res.domainStateRecord), - }) - } - } -} - -function isValidRequest( - request: Request<{}, {}, unknown> -): request is Request<{}, {}, DomainRestrictedSignatureRequest> { - return domainRestrictedSignatureRequestSchema(DomainSchema).is(request.body) -} - -function nonceCheck( - domainStateRecord: DomainStateRecord, - body: DomainRestrictedSignatureRequest, - logger: Logger -): boolean { - const nonce: EIP712Optional = body.options.nonce - if (!nonce.defined) { - logger.info('Nonce is undefined') - return false - } - return nonce.value >= domainStateRecord.counter -} - -async function sign( - domain: Domain, - blindedMessage: string, - key: Key, - logger: Logger, - keyProvider: KeyProvider -): Promise { - let privateKey: string - try { - privateKey = await keyProvider.getPrivateKeyOrFetchFromStore(key) - } catch (err) { - logger.error({ key }, 'Requested key version not supported') - logger.error(err) - throw new Error(WarningMessage.INVALID_KEY_VERSION_REQUEST) - } - - const server = new ThresholdPoprfServer(Buffer.from(privateKey, 'hex')) - return server.blindPartialEval(domainHash(domain), Buffer.from(blindedMessage, 'base64')) -} diff --git a/packages/phone-number-privacy/signer/src/domain/services/quota.ts b/packages/phone-number-privacy/signer/src/domain/services/quota.ts deleted file mode 100644 index 4a05475cf2..0000000000 --- a/packages/phone-number-privacy/signer/src/domain/services/quota.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - checkSequentialDelayRateLimit, - DomainQuotaStatusRequest, - DomainRestrictedSignatureRequest, - ErrorMessage, - isSequentialDelayDomain, - SequentialDelayDomain, -} from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Knex } from 'knex' -import { - DomainStateRecord, - toDomainStateRecord, - toSequentialDelayDomainState, -} from '../../common/database/models/domain-state' -import { - getDomainStateRecordOrEmpty, - updateDomainStateRecord, -} from '../../common/database/wrappers/domain-state' -import { OdisQuotaStatusResult } from '../../common/quota' - -declare type QuotaDependentDomainRequest = - | DomainQuotaStatusRequest - | DomainRestrictedSignatureRequest - -export class DomainQuotaService { - constructor(readonly db: Knex) {} - - async checkAndUpdateQuotaStatus( - state: DomainStateRecord, - domain: SequentialDelayDomain, - trx: Knex.Transaction, - logger: Logger, - attemptTime?: number - ): Promise> { - // Timestamp precision is lowered to seconds to reduce the chance of effective timing attacks. - attemptTime = attemptTime ?? Math.floor(Date.now() / 1000) - if (isSequentialDelayDomain(domain)) { - const result = checkSequentialDelayRateLimit( - domain, - attemptTime, - toSequentialDelayDomainState(state, attemptTime) - ) - if (result.accepted) { - const newState = toDomainStateRecord(domain, result.state) - // Persist the updated domain quota to the database. - // This will trigger an insert if its the first update to the domain instance. - await updateDomainStateRecord(this.db, domain, newState, trx, logger) - return { sufficient: true, state: newState } - } - // If the result was rejected, the domainStateRecord does not change - return { sufficient: false, state } - } else { - throw new Error(ErrorMessage.UNSUPPORTED_DOMAIN) - } - } - - async getQuotaStatus( - domain: SequentialDelayDomain, - logger: Logger, - trx?: Knex.Transaction - ): Promise { - return getDomainStateRecordOrEmpty(this.db, domain, logger, trx) - } -} diff --git a/packages/phone-number-privacy/signer/src/index.ts b/packages/phone-number-privacy/signer/src/index.ts deleted file mode 100644 index 573a27e48f..0000000000 --- a/packages/phone-number-privacy/signer/src/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { getContractKitWithAgent, rootLogger } from '@celo/phone-number-privacy-common' -import { CronJob } from 'cron' -import { Knex } from 'knex' -import { initDatabase } from './common/database/database' -import { initKeyProvider } from './common/key-management/key-provider' -import { KeyProvider } from './common/key-management/key-provider-base' -import { config, DEV_MODE, SupportedDatabase, SupportedKeystore } from './config' -import { DefaultPnpRequestService, MockPnpRequestService } from './pnp/services/request-service' -import { startSigner } from './server' - -require('dotenv').config() - -if (DEV_MODE) { - config.db.type = SupportedDatabase.Sqlite - config.keystore.type = SupportedKeystore.MOCK_SECRET_MANAGER -} -let databasePrunner: CronJob - -async function start() { - const logger = rootLogger(config.serviceName) - logger.info(`Starting. Dev mode: ${DEV_MODE}`) - const db = await initDatabase(config) - const keyProvider: KeyProvider = await initKeyProvider(config) - const server = startSigner(config, db, keyProvider, getContractKitWithAgent(config.blockchain)) - - logger.info('Starting database Prunner job') - launchRequestPrunnerJob(db) - - logger.info('Starting server') - const port = config.server.port ?? 0 - const backupTimeout = config.timeout * 1.2 - server - .listen(port, () => { - logger.info(`Server is listening on port ${port}`) - }) - .setTimeout(backupTimeout) -} - -function launchRequestPrunnerJob(db: Knex) { - const ctx = { - url: '', - logger: rootLogger(config.serviceName), - errors: [], - } - const pnpRequestService = config.shouldMockRequestService - ? new MockPnpRequestService() - : new DefaultPnpRequestService(db) - databasePrunner = new CronJob({ - cronTime: config.requestPrunningJobCronPattern, - onTick: async () => { - ctx.logger.info('Prunning database requests') - await pnpRequestService.removeOldRequests(config.requestPrunningDays, ctx) - }, - timeZone: 'UTC', - runOnInit: config.requestPrunningAtServerStart, - }) - databasePrunner.start() -} - -start().catch((err) => { - const logger = rootLogger(config.serviceName) - logger.error({ err }, 'Fatal error occured. Exiting') - databasePrunner?.stop() - process.exit(1) -}) - -export { initDatabase } from './common/database/database' -export { initKeyProvider } from './common/key-management/key-provider' -export { config, SupportedDatabase, SupportedKeystore } from './config' -export * from './server' diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/action.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/action.ts deleted file mode 100644 index fb9ffa180e..0000000000 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/action.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { - authenticateUser, - AuthenticationMethod, - ErrorType, - hasValidAccountParam, - isBodyReasonablySized, - PnpQuotaRequest, - PnpQuotaRequestSchema, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { Request } from 'express' -import { errorResult, ResultHandler } from '../../../common/handler' -import { Counters } from '../../../common/metrics' -import { getSignerVersion } from '../../../config' -import { AccountService } from '../../services/account-service' -import { PnpRequestService } from '../../services/request-service' - -export function pnpQuota( - requestService: PnpRequestService, - accountService: AccountService -): ResultHandler { - return async (request, response) => { - const logger = response.locals.logger - - if (!isValidRequest(request)) { - return errorResult(400, WarningMessage.INVALID_INPUT) - } - - const warnings: ErrorType[] = [] - const ctx = { - url: request.url, - logger, - errors: warnings, - } - - const account = await accountService.getAccount(request.body.account) - - if (request.body.authenticationMethod === AuthenticationMethod.WALLET_KEY) { - Counters.requestsWithWalletAddress.inc() - } - - if (!(await authenticateUser(request, logger, async (_) => account.dek, warnings))) { - return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) - } - - const usedQuota = await requestService.getUsedQuotaForAccount(request.body.account, ctx) - - return { - status: 200, - body: { - success: true, - version: getSignerVersion(), - performedQueryCount: usedQuota, - totalQuota: account.pnpTotalQuota, - warnings, - }, - } - } -} - -function isValidRequest( - request: Request<{}, {}, unknown> -): request is Request<{}, {}, PnpQuotaRequest> { - return ( - PnpQuotaRequestSchema.is(request.body) && - hasValidAccountParam(request.body) && - isBodyReasonablySized(request.body) - ) -} diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.ts deleted file mode 100644 index f5dd8b4ce4..0000000000 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { - authenticateUser, - AuthenticationMethod, - ErrorMessage, - ErrorType, - getRequestKeyVersion, - hasValidAccountParam, - hasValidBlindedPhoneNumberParam, - isBodyReasonablySized, - KEY_VERSION_HEADER, - requestHasValidKeyVersion, - SignMessageRequest, - SignMessageRequestSchema, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Request } from 'express' -import { computeBlindedSignature } from '../../../common/bls/bls-cryptography-client' -import { errorResult, ResultHandler } from '../../../common/handler' -import { DefaultKeyName, Key, KeyProvider } from '../../../common/key-management/key-provider-base' -import { Counters, Histograms } from '../../../common/metrics' -import { traceAsyncFunction } from '../../../common/tracing-utils' -import { getSignerVersion, SignerConfig } from '../../../config' -import { AccountService } from '../../services/account-service' -import { PnpRequestService } from '../../services/request-service' - -export function pnpSign( - config: SignerConfig, - requestService: PnpRequestService, - accountService: AccountService, - keyProvider: KeyProvider -): ResultHandler { - return async (request, response) => { - const logger = response.locals.logger - - if (!isValidRequest(request)) { - return errorResult(400, WarningMessage.INVALID_INPUT) - } - - if (!requestHasValidKeyVersion(request, logger)) { - return errorResult(400, WarningMessage.INVALID_KEY_VERSION_REQUEST) - } - - const warnings: ErrorType[] = [] - const ctx = { - url: request.url, - logger, - errors: warnings, - } - - const account = await accountService.getAccount(request.body.account) - - if (request.body.authenticationMethod === AuthenticationMethod.WALLET_KEY) { - Counters.requestsWithWalletAddress.inc() - } - - if (!(await authenticateUser(request, logger, async (_) => account.dek, warnings))) { - return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) - } - - let usedQuota = await requestService.getUsedQuotaForAccount(request.body.account, ctx) - - const duplicateRequest = await requestService.getDuplicateRequest( - request.body.account, - request.body.blindedQueryPhoneNumber, - ctx - ) - - Histograms.userRemainingQuotaAtRequest - .labels(ctx.url) - .observe(account.pnpTotalQuota - usedQuota) - - if (!duplicateRequest && account.pnpTotalQuota <= usedQuota) { - logger.warn({ usedQuota, totalQuota: account.pnpTotalQuota }, 'No remaining quota') - - if (bypassQuotaForE2ETesting(config.test_quota_bypass_percentage, request.body)) { - Counters.testQuotaBypassedRequests.inc() - logger.info(request.body, 'Request will bypass quota check for e2e testing') - } else { - return errorResult(403, WarningMessage.EXCEEDED_QUOTA, { - performedQueryCount: usedQuota, - totalQuota: account.pnpTotalQuota, - }) - } - } - - const key: Key = { - version: - getRequestKeyVersion(request, logger) ?? config.keystore.keys.phoneNumberPrivacy.latest, - name: DefaultKeyName.PHONE_NUMBER_PRIVACY, - } - - let signature: string - if (duplicateRequest && duplicateRequest.signature?.length) { - signature = duplicateRequest.signature - } else { - try { - signature = await sign(request.body.blindedQueryPhoneNumber, key, keyProvider, logger) - } catch (err) { - logger.error({ err }, 'catch error on signing') - - return errorResult(500, ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, { - performedQueryCount: usedQuota, - totalQuota: account.pnpTotalQuota, - }) - } - } - - if (!duplicateRequest) { - await requestService.recordRequest( - account.address, - request.body.blindedQueryPhoneNumber, - signature, - ctx - ) - if (!bypassQuotaForE2ETesting(config.test_quota_bypass_percentage, request.body)) { - usedQuota++ - } - } else { - Counters.duplicateRequests.inc() - logger.info('Request already exists in db. Will service request without charging quota.') - warnings.push(WarningMessage.DUPLICATE_REQUEST_TO_GET_PARTIAL_SIG) - } - - // Send Success response - response.set(KEY_VERSION_HEADER, key.version.toString()) - return { - status: 200, - body: { - success: true as true, - version: getSignerVersion(), - signature, - performedQueryCount: usedQuota, - totalQuota: account.pnpTotalQuota, - warnings, - }, - } - } -} - -async function sign( - blindedMessage: string, - key: Key, - keyProvider: KeyProvider, - logger: Logger -): Promise { - let privateKey: string - return traceAsyncFunction('pnpSign', async () => { - try { - privateKey = await keyProvider.getPrivateKeyOrFetchFromStore(key) - } catch (err) { - logger.info({ key }, 'Requested key version not supported') - logger.error(err) - throw new Error(WarningMessage.INVALID_KEY_VERSION_REQUEST) - } - return computeBlindedSignature(blindedMessage, privateKey, logger) - }) -} - -function isValidRequest( - request: Request<{}, {}, unknown> -): request is Request<{}, {}, SignMessageRequest> { - return ( - SignMessageRequestSchema.is(request.body) && - hasValidAccountParam(request.body) && - hasValidBlindedPhoneNumberParam(request.body) && - isBodyReasonablySized(request.body) - ) -} - -function bypassQuotaForE2ETesting( - bypassQuotaPercentage: number, - requestBody: SignMessageRequest -): boolean { - const sessionID = Number(requestBody.sessionID) // TODO revisit whether to remove sessionID - return !Number.isNaN(sessionID) && sessionID % 100 < bypassQuotaPercentage -} diff --git a/packages/phone-number-privacy/signer/src/pnp/services/account-service.ts b/packages/phone-number-privacy/signer/src/pnp/services/account-service.ts deleted file mode 100644 index 74e75fd3f9..0000000000 --- a/packages/phone-number-privacy/signer/src/pnp/services/account-service.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { ContractKit } from '@celo/contractkit' -import { ErrorMessage } from '@celo/phone-number-privacy-common' -import BigNumber from 'bignumber.js' -import Logger from 'bunyan' -import { LRUCache } from 'lru-cache' -import { OdisError, wrapError } from '../../common/error' -import { traceAsyncFunction } from '../../common/tracing-utils' -import { getDEK, getOnChainOdisPayments } from '../../common/web3/contracts' -import { config } from '../../config' - -export interface PnpAccount { - dek: string // onChain - address: string // onChain - pnpTotalQuota: number // onChain -} - -export interface AccountService { - getAccount(address: string): Promise -} - -interface CachedValue { - dek: string - pnpTotalQuota: number -} - -export class CachingAccountService implements AccountService { - private cache: LRUCache - constructor(baseService: AccountService) { - this.cache = new LRUCache({ - max: 500, - ttl: 5 * 1000, // 5 seconds - allowStale: true, - fetchMethod: async (address: string) => { - const account = await baseService.getAccount(address) - return { dek: account.dek, pnpTotalQuota: account.pnpTotalQuota } - }, - }) - } - - getAccount(address: string): Promise { - return traceAsyncFunction('CachingAccountService - getAccount', async () => { - const value = await this.cache.fetch(address) - - if (value === undefined) { - throw new OdisError(ErrorMessage.FULL_NODE_ERROR) - } - return { - address, - dek: value.dek, - pnpTotalQuota: value.pnpTotalQuota, - } - }) - } -} - -// tslint:disable-next-line:max-classes-per-file -export class ContractKitAccountService implements AccountService { - constructor(private readonly logger: Logger, private readonly kit: ContractKit) {} - - async getAccount(address: string): Promise { - return traceAsyncFunction('ContractKitAccountService - getAccount', async () => { - const dek = await wrapError( - getDEK(this.kit, this.logger, address), - ErrorMessage.FAILURE_TO_GET_DEK - ) - - const { queryPriceInCUSD } = config.quota - const totalPaidInWei = await wrapError( - getOnChainOdisPayments(this.kit, this.logger, address), - ErrorMessage.FAILURE_TO_GET_TOTAL_QUOTA - ) - const totalQuotaBN = totalPaidInWei - .div(queryPriceInCUSD.times(new BigNumber(1e18))) - .integerValue(BigNumber.ROUND_DOWN) - - // If any account hits an overflow here, we need to redesign how - // quota/queries are computed anyways. - const pnpTotalQuota = totalQuotaBN.toNumber() - - return { - address, - dek, - pnpTotalQuota, - } - }) - } -} - -// tslint:disable-next-line:max-classes-per-file -export class MockAccountService implements AccountService { - constructor(private readonly mockDek: string, private readonly mockTotalQuota: number) {} - - async getAccount(address: string): Promise { - return { - dek: this.mockDek, - address, - pnpTotalQuota: this.mockTotalQuota, - } - } -} diff --git a/packages/phone-number-privacy/signer/src/pnp/services/request-service.ts b/packages/phone-number-privacy/signer/src/pnp/services/request-service.ts deleted file mode 100644 index 597a714fe9..0000000000 --- a/packages/phone-number-privacy/signer/src/pnp/services/request-service.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { ErrorMessage } from '@celo/phone-number-privacy-common' -import { Knex } from 'knex' -import { Context } from '../../common/context' -import { PnpSignRequestRecord } from '../../common/database/models/request' -import { getPerformedQueryCount, incrementQueryCount } from '../../common/database/wrappers/account' -import { - deleteRequestsOlderThan, - getRequestIfExists, - insertRequest, -} from '../../common/database/wrappers/request' -import { wrapError } from '../../common/error' -import { traceAsyncFunction } from '../../common/tracing-utils' - -export interface PnpRequestService { - recordRequest( - address: string, - blindedQuery: string, - signature: string, - ctx: Context - ): Promise - getUsedQuotaForAccount(address: string, ctx: Context): Promise - getDuplicateRequest( - address: string, - blindedQuery: string, - ctx: Context - ): Promise - removeOldRequests(daysToKeep: number, ctx: Context): Promise -} - -export class DefaultPnpRequestService implements PnpRequestService { - constructor(readonly db: Knex) {} - - public async recordRequest( - account: string, - blindedQueryPhoneNumber: string, - signature: string, - ctx: Context - ): Promise { - return traceAsyncFunction('DefaultPnpRequestService - recordRequest', () => - this.db.transaction(async (trx) => { - await insertRequest(this.db, account, blindedQueryPhoneNumber, signature, ctx.logger, trx) - await incrementQueryCount(this.db, account, ctx.logger, trx) - }) - ) - } - - public async getUsedQuotaForAccount(account: string, ctx: Context): Promise { - return traceAsyncFunction('DefaultPnpRequestService - getUsedQuotaForAccount', () => - wrapError( - getPerformedQueryCount(this.db, account, ctx.logger), - ErrorMessage.FAILURE_TO_GET_PERFORMED_QUERY_COUNT - ) - ) - } - - public async getDuplicateRequest( - account: string, - blindedQueryPhoneNumber: string, - ctx: Context - ): Promise { - try { - const res = await getRequestIfExists(this.db, account, blindedQueryPhoneNumber, ctx.logger) - return res - } catch (err) { - ctx.logger.error(err, 'Failed to check if request already exists in db') - return undefined - } - } - - public async removeOldRequests(daysToKeep: number, ctx: Context): Promise { - if (daysToKeep < 0) { - ctx.logger.error( - { daysToKeep }, - 'RemoveOldRequests - DaysToKeep should be bigger than or equal to zero' - ) - return 0 - } - const since: Date = new Date(Date.now() - daysToKeep * 24 * 60 * 60 * 1000) - return traceAsyncFunction('DefaultPnpRequestService - removeOldRequests', () => - deleteRequestsOlderThan(this.db, since, ctx.logger) - ) - } -} - -// tslint:disable-next-line:max-classes-per-file -export class MockPnpRequestService implements PnpRequestService { - public async recordRequest( - account: string, - blindedQueryPhoneNumber: string, - signature: string, - ctx: Context - ): Promise { - ctx.logger.info( - { account, blindedQueryPhoneNumber, signature }, - 'MockPnpRequestService - recordRequest' - ) - return - } - - public async getUsedQuotaForAccount(account: string, ctx: Context): Promise { - ctx.logger.info({ account }, 'MockPnpRequestService - getUsedQuotaForAccount') - return 0 - } - - public async getDuplicateRequest( - account: string, - blindedQueryPhoneNumber: string, - ctx: Context - ): Promise { - ctx.logger.info( - { account, blindedQueryPhoneNumber }, - 'MockPnpRequestService - isDuplicateRequest' - ) - return undefined - } - - public async removeOldRequests(daysToKeep: number, ctx: Context): Promise { - ctx.logger.info({ daysToKeep }, 'MockPnpRequestService - removeOldRequests') - return 0 - } -} diff --git a/packages/phone-number-privacy/signer/src/server.ts b/packages/phone-number-privacy/signer/src/server.ts deleted file mode 100644 index 2933cf7c22..0000000000 --- a/packages/phone-number-privacy/signer/src/server.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { ContractKit } from '@celo/contractkit' -import { - getContractKitWithAgent, - loggerMiddleware, - OdisRequest, - rootLogger, - SignerEndpoint, -} from '@celo/phone-number-privacy-common' -import express, { Express, RequestHandler } from 'express' -import fs from 'fs' -import https from 'https' -import { Knex } from 'knex' -import { IncomingMessage, ServerResponse } from 'node:http' -import * as PromClient from 'prom-client' -import { - catchErrorHandler, - connectionClosedHandler, - disabledHandler, - Locals, - meteringHandler, - ResultHandler, - resultHandler, - timeoutHandler, - tracingHandler, -} from './common/handler' -import { KeyProvider } from './common/key-management/key-provider-base' -import { Histograms } from './common/metrics' -import { getSignerVersion, SignerConfig } from './config' -import { domainDisable } from './domain/endpoints/disable/action' -import { domainQuota } from './domain/endpoints/quota/action' -import { domainSign } from './domain/endpoints/sign/action' -import { DomainQuotaService } from './domain/services/quota' -import { pnpQuota } from './pnp/endpoints/quota/action' -import { pnpSign } from './pnp/endpoints/sign/action' -import { - CachingAccountService, - ContractKitAccountService, - MockAccountService, -} from './pnp/services/account-service' -import { DefaultPnpRequestService, MockPnpRequestService } from './pnp/services/request-service' - -require('events').EventEmitter.defaultMaxListeners = 15 - -export function startSigner( - config: SignerConfig, - db: Knex, - keyProvider: KeyProvider, - kit?: ContractKit -): Express | https.Server { - const logger = rootLogger(config.serviceName) - - kit = kit ?? getContractKitWithAgent(config.blockchain) - - logger.info('Creating signer express server') - const app = express() - app.use(express.json({ limit: '0.2mb' }) as RequestHandler, loggerMiddleware(config.serviceName)) - - app.get(SignerEndpoint.STATUS, (_req, res) => { - res.status(200).json({ - version: getSignerVersion(), - }) - }) - - app.get(SignerEndpoint.METRICS, (_req, res) => { - res.send(PromClient.register.metrics()) - }) - - const baseAccountService = config.shouldMockAccountService - ? new MockAccountService(config.mockDek, config.mockTotalQuota) - : new ContractKitAccountService(logger, kit) - - const accountService = new CachingAccountService(baseAccountService) - - const pnpRequestService = config.shouldMockRequestService - ? new MockPnpRequestService() - : new DefaultPnpRequestService(db) - const domainQuotaService = new DomainQuotaService(db) - - logger.info('Right before adding meteredSignerEndpoints') - - const { - timeout, - api: { domains, phoneNumberPrivacy }, - } = config - - app.post( - SignerEndpoint.PNP_SIGN, - createHandler( - timeout, - phoneNumberPrivacy.enabled, - pnpSign(config, pnpRequestService, accountService, keyProvider) - ) - ) - app.post( - SignerEndpoint.PNP_QUOTA, - createHandler(timeout, phoneNumberPrivacy.enabled, pnpQuota(pnpRequestService, accountService)) - ) - app.post( - SignerEndpoint.DOMAIN_QUOTA_STATUS, - createHandler(timeout, domains.enabled, domainQuota(domainQuotaService)) - ) - app.post( - SignerEndpoint.DOMAIN_SIGN, - createHandler(timeout, domains.enabled, domainSign(db, config, domainQuotaService, keyProvider)) - ) - app.post( - SignerEndpoint.DISABLE_DOMAIN, - createHandler(timeout, domains.enabled, domainDisable(db)) - ) - - const sslOptions = getSslOptions(config) - if (sslOptions) { - return https.createServer(sslOptions, app) - } else { - return app - } -} - -function getSslOptions(config: SignerConfig) { - const logger = rootLogger(config.serviceName) - const { sslKeyPath, sslCertPath } = config.server - - if (!sslKeyPath || !sslCertPath) { - logger.info('No SSL configs specified') - return null - } - - if (!fs.existsSync(sslKeyPath) || !fs.existsSync(sslCertPath)) { - logger.error('SSL cert files not found') - return null - } - - return { - key: fs.readFileSync(sslKeyPath), - cert: fs.readFileSync(sslCertPath), - } -} - -function createHandler( - timeoutMs: number, - enabled: boolean, - action: ResultHandler -): RequestHandler<{}, {}, R, {}, Locals> { - return catchErrorHandler( - tracingHandler( - meteringHandler( - Histograms.responseLatency, - timeoutHandler( - timeoutMs, - enabled ? connectionClosedHandler(resultHandler(action)) : disabledHandler - ) - ) - ) - ) -} diff --git a/packages/phone-number-privacy/signer/src/tracing.ts b/packages/phone-number-privacy/signer/src/tracing.ts deleted file mode 100644 index 6bdcd64d32..0000000000 --- a/packages/phone-number-privacy/signer/src/tracing.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node - getNodeAutoInstrumentations (above) includes all available auto-tracing - instrumentations for node (Knex, Express, Http, tcp, ...). - This may lead to super-verbose traces. We can just comment the getNodeAutoInstrumentations - line above and just add the instrumentations we care like commented lines below. -*/ -import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node' -import { JaegerExporter } from '@opentelemetry/exporter-jaeger' -import { registerInstrumentations } from '@opentelemetry/instrumentation' -import { Resource } from '@opentelemetry/resources' -import { BatchSpanProcessor, NodeTracerProvider } from '@opentelemetry/sdk-trace-node' -/* - Some instrumentations included in auto-instrumentations-node: - - https://www.npmjs.com/package/@opentelemetry/sdk-trace-web - - https://www.npmjs.com/package/@opentelemetry/instrumentation-express - - https://www.npmjs.com/package/@opentelemetry/instrumentation-http - - https://www.npmjs.com/package/@opentelemetry/instrumentation-knex -*/ -// import { WebTracerProvider } from '@opentelemetry/sdk-trace-web' -// import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express' -// import { HttpInstrumentation } from '@opentelemetry/instrumentation-http' -// import { KnexInstrumentation } from '@opentelemetry/instrumentation-knex' -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' - -const options = { - tags: [], - endpoint: process.env.TRACER_ENDPOINT, - // 'http://grafana-agent.grafana-agent:14268/api/traces', -} - -// Optionally register automatic instrumentation libraries -registerInstrumentations({ - instrumentations: [ - getNodeAutoInstrumentations({ - // load custom configuration for http instrumentation - '@opentelemetry/instrumentation-http': { - ignoreIncomingPaths: [ - /\/status/, - /\/metrics/, - /\/metadata\/.*/, - /\/secrets\/.*/, - /\/ecp\/.*/, - ], - }, - }), - /* - How to add specific instrumentations instead of the - all-included auto-tracing getNodeAutoInstrumentations - */ - // new HttpInstrumentation({ - // ignoreIncomingPaths: [ - // /\/status/, - // /\/metrics/, - // /\/metadata\/.*/, - // /\/secrets\/.*/, - // /\/ecp\/.*/, - // ], - // }), - // new ExpressInstrumentation(), - // new KnexInstrumentation(), - ], -}) - -const resource = Resource.default().merge( - new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: process.env.TRACING_SERVICE_NAME, - // 'testing-signer-tracing', - [SemanticResourceAttributes.SERVICE_VERSION]: '0.1.0', // should be the same as package.json version? - }) -) - -const provider = new NodeTracerProvider({ - resource, -}) -const exporter = new JaegerExporter(options) -const processor = new BatchSpanProcessor(exporter) -provider.addSpanProcessor(processor) - -provider.register() diff --git a/packages/phone-number-privacy/signer/test/end-to-end/disabled-apis.test.ts b/packages/phone-number-privacy/signer/test/end-to-end/disabled-apis.test.ts deleted file mode 100644 index 1014df19aa..0000000000 --- a/packages/phone-number-privacy/signer/test/end-to-end/disabled-apis.test.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { - DisableDomainRequest, - disableDomainRequestEIP712, - DisableDomainResponse, - DomainIdentifiers, - DomainQuotaStatusRequest, - domainQuotaStatusRequestEIP712, - DomainQuotaStatusResponse, - DomainRequestTypeTag, - DomainRestrictedSignatureRequest, - domainRestrictedSignatureRequestEIP712, - genSessionID, - PnpQuotaRequest, - PnpQuotaResponse, - SequentialDelayDomain, - SignerEndpoint, - SignMessageRequest, - TestUtils, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { defined, noBool, noNumber, noString } from '@celo/utils/lib/sign-typed-data-utils' -import { LocalWallet } from '@celo/wallet-local' -import 'isomorphic-fetch' - -require('dotenv').config() - -const { ACCOUNT_ADDRESS1, BLINDED_PHONE_NUMBER, PRIVATE_KEY1 } = TestUtils.Values -const { getPnpRequestAuthorization } = TestUtils.Utils - -const ODIS_SIGNER = process.env.ODIS_SIGNER_SERVICE_URL - -jest.setTimeout(30000) - -const expectedVersion = process.env.DEPLOYED_SIGNER_SERVICE_VERSION! - -// These tests should be run when the individual APIs are disabled. -// When run against enabled APIs, they should fail. -describe('Running against a deployed service with disabled APIs', () => { - beforeAll(() => { - console.log('ODIS_SIGNER: ' + ODIS_SIGNER) - }) - - it('Service is deployed at correct version', async () => { - const response = await fetch(ODIS_SIGNER + SignerEndpoint.STATUS, { method: 'GET' }) - const body = await response.json() - // This checks against local package.json version, change if necessary - expect(response.status).toBe(200) - expect(body.version).toBe(expectedVersion) - }) - - describe('when DOMAINS API is disabled', () => { - const wallet = new LocalWallet() - wallet.addAccount(PRIVATE_KEY1) - const walletAddress = wallet.getAccounts()[0] - - const authenticatedDomain: SequentialDelayDomain = { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: [{ delay: 0, resetTimer: noBool, batchSize: defined(2), repetitions: defined(10) }], - address: defined(walletAddress), - salt: defined('himalayanPink'), - } - - it(`${SignerEndpoint.DISABLE_DOMAIN} should respond with 503`, async () => { - const req: DisableDomainRequest = { - type: DomainRequestTypeTag.DISABLE, - domain: authenticatedDomain, - options: { - signature: noString, - nonce: noNumber, - }, - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await wallet.signTypedData(walletAddress, disableDomainRequestEIP712(req)) - ) - const body = JSON.stringify(req) - const response = await fetch(ODIS_SIGNER + SignerEndpoint.DISABLE_DOMAIN, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body, - }) - expect(response.status).toBe(503) - const responseBody: DisableDomainResponse = await response.json() - expect(responseBody).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - - it(`${SignerEndpoint.DOMAIN_QUOTA_STATUS} should respond with 503`, async () => { - const req: DomainQuotaStatusRequest = { - type: DomainRequestTypeTag.QUOTA, - domain: authenticatedDomain, - options: { - signature: noString, - nonce: noNumber, - }, - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await wallet.signTypedData(walletAddress, domainQuotaStatusRequestEIP712(req)) - ) - const body = JSON.stringify(req) - const response = await fetch(ODIS_SIGNER + SignerEndpoint.DOMAIN_QUOTA_STATUS, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body, - }) - expect(response.status).toBe(503) - const responseBody: DomainQuotaStatusResponse = await response.json() - expect(responseBody).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - - it(`${SignerEndpoint.DOMAIN_SIGN} should respond with 503`, async () => { - const req: DomainRestrictedSignatureRequest = { - type: DomainRequestTypeTag.SIGN, - domain: authenticatedDomain, - options: { - signature: noString, - nonce: noNumber, - }, - // The message shouldn't actually matter here - blindedMessage: BLINDED_PHONE_NUMBER, - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await wallet.signTypedData(walletAddress, domainRestrictedSignatureRequestEIP712(req)) - ) - const body = JSON.stringify(req) - const response = await fetch(ODIS_SIGNER + SignerEndpoint.DOMAIN_SIGN, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body, - }) - expect(response.status).toBe(503) - const responseBody: DomainQuotaStatusResponse = await response.json() - expect(responseBody).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - }) - - describe('when PNP API is disabled', () => { - it(`${SignerEndpoint.PNP_QUOTA} should respond with 503`, async () => { - const req: PnpQuotaRequest = { - account: ACCOUNT_ADDRESS1, - } - const body = JSON.stringify(req) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const response = await fetch(ODIS_SIGNER + SignerEndpoint.PNP_QUOTA, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: authorization, - }, - body, - }) - expect(response.status).toBe(503) - const responseBody: PnpQuotaResponse = await response.json() - expect(responseBody).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - - it(`${SignerEndpoint.PNP_SIGN} should respond with 503`, async () => { - const req: SignMessageRequest = { - account: ACCOUNT_ADDRESS1, - blindedQueryPhoneNumber: BLINDED_PHONE_NUMBER, - } - const body = JSON.stringify(req) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const response = await fetch(ODIS_SIGNER + SignerEndpoint.PNP_SIGN, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: authorization, - }, - body, - }) - expect(response.status).toBe(503) - const responseBody: PnpQuotaResponse = await response.json() - expect(responseBody).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - }) -}) diff --git a/packages/phone-number-privacy/signer/test/end-to-end/domain.test.ts b/packages/phone-number-privacy/signer/test/end-to-end/domain.test.ts deleted file mode 100644 index 019da14223..0000000000 --- a/packages/phone-number-privacy/signer/test/end-to-end/domain.test.ts +++ /dev/null @@ -1,620 +0,0 @@ -import { - DisableDomainRequest, - disableDomainRequestEIP712, - DisableDomainResponseFailure, - DisableDomainResponseSuccess, - DomainEndpoint, - domainHash, - DomainIdentifiers, - DomainQuotaStatusRequest, - domainQuotaStatusRequestEIP712, - DomainQuotaStatusResponseFailure, - DomainQuotaStatusResponseSuccess, - DomainRequestTypeTag, - DomainRestrictedSignatureRequest, - domainRestrictedSignatureRequestEIP712, - DomainRestrictedSignatureResponseFailure, - DomainRestrictedSignatureResponseSuccess, - genSessionID, - KEY_VERSION_HEADER, - SequentialDelayDomain, - SequentialDelayStage, - SignerEndpoint, - TestUtils, - ThresholdPoprfClient, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { DomainRequest } from '@celo/phone-number-privacy-common/src' -import { defined, noBool, noNumber, noString } from '@celo/utils/lib/sign-typed-data-utils' -import { LocalWallet } from '@celo/wallet-local' -import 'isomorphic-fetch' -import { getTestParamsForContext } from './utils' -const { ACCOUNT_ADDRESS1, PRIVATE_KEY1 } = TestUtils.Values - -require('dotenv').config() - -jest.setTimeout(60000) - -const expectedVersion = process.env.DEPLOYED_SIGNER_SERVICE_VERSION! - -const ODIS_SIGNER_URL = process.env.ODIS_SIGNER_SERVICE_URL - -const contextSpecificParams = getTestParamsForContext() -console.log(`Blockchain provider: ${contextSpecificParams.blockchainProviderURL}`) -console.log(`Domains public polynomial: ${contextSpecificParams.domainsPolynomial}`) -console.log(`Domains pubKey: ${contextSpecificParams.domainsPubKey}`) - -describe(`Running against service deployed at ${ODIS_SIGNER_URL}`, () => { - const wallet = new LocalWallet() - wallet.addAccount(PRIVATE_KEY1) - - const disableSalt = 'himalayanPink-disable' - const quotaSalt = 'himalayanPink-quota' - const signSalt = 'himalayanPink-sign' - - it('Service is deployed at correct version', async () => { - const response = await fetch(ODIS_SIGNER_URL + SignerEndpoint.STATUS, { - method: 'GET', - }) - expect(response.status).toBe(200) - const body = await response.json() - // This checks against local package.json version, change if necessary - expect(body.version).toBe(expectedVersion) - }) - - describe(`${SignerEndpoint.DISABLE_DOMAIN}`, () => { - it('Should respond with 200 on valid request for new domain', async () => { - const req = await disableRequest(wallet, ACCOUNT_ADDRESS1, `${disableSalt}-${Date.now()}`) - const res = await queryDomainEndpoint(req, SignerEndpoint.DISABLE_DOMAIN) - expect(res.status).toBe(200) - const resBody: DisableDomainResponseSuccess = await res.json() - expect(resBody).toEqual({ - success: true, - version: resBody.version, - status: { - disabled: true, - counter: 0, - timer: 0, - now: resBody.status.now, - }, - }) - }) - - it('Should respond with 200 on valid request for already disabled domain', async () => { - const req = await disableRequest(wallet, ACCOUNT_ADDRESS1, disableSalt) - const res = await queryDomainEndpoint(req, SignerEndpoint.DISABLE_DOMAIN) - expect(res.status).toBe(200) - const resBody: DisableDomainResponseSuccess = await res.json() - expect(resBody).toEqual({ - success: true, - version: resBody.version, - status: { - disabled: true, - counter: 0, - timer: 0, - now: resBody.status.now, - }, - }) - }) - - it('Should respond with 400 on missing request fields', async () => { - const badRequest = await disableRequest(wallet, ACCOUNT_ADDRESS1, disableSalt) - // @ts-ignore Intentionally deleting required field - delete badRequest.domain.version - const res = await queryDomainEndpoint(badRequest, SignerEndpoint.DISABLE_DOMAIN) - expect(res.status).toBe(400) - const resBody: DisableDomainResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: resBody.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on unknown domain', async () => { - const badRequest = await disableRequest(wallet, ACCOUNT_ADDRESS1, disableSalt) - // @ts-ignore UnknownDomain is (intentionally) not a valid domain identifier. - badRequest.domain.name = 'UnknownDomain' - const res = await queryDomainEndpoint(badRequest, SignerEndpoint.DISABLE_DOMAIN) - expect(res.status).toBe(400) - const resBody: DisableDomainResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: resBody.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on bad encoding', async () => { - const badRequest = await disableRequest(wallet, ACCOUNT_ADDRESS1, disableSalt) - // @ts-ignore Intentionally not JSON - badRequest.domain = 'Freddy' - const res = await queryDomainEndpoint(badRequest, SignerEndpoint.DISABLE_DOMAIN) - expect(res.status).toBe(400) - const resBody: DisableDomainResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: resBody.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed auth', async () => { - const badRequest = await disableRequest(wallet, ACCOUNT_ADDRESS1, disableSalt) - badRequest.domain.salt = defined('badSalt') - const res = await queryDomainEndpoint(badRequest, SignerEndpoint.DISABLE_DOMAIN) - expect(res.status).toBe(401) - const resBody: DisableDomainResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: resBody.version, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - }) - - describe(`${SignerEndpoint.DOMAIN_QUOTA_STATUS}`, () => { - // This request gets repeated over time - it('Should respond with 200 on valid request', async () => { - const req = await quotaRequest(wallet, ACCOUNT_ADDRESS1, quotaSalt) - const res = await queryDomainEndpoint(req, SignerEndpoint.DOMAIN_QUOTA_STATUS) - expect(res.status).toBe(200) - const resBody: DomainQuotaStatusResponseSuccess = await res.json() - expect(resBody).toEqual({ - success: true, - version: expectedVersion, - status: { disabled: false, counter: 0, timer: 0, now: resBody.status.now }, - }) - }) - - it('Should respond with 200 on valid request for disabled domain', async () => { - const req = await quotaRequest(wallet, ACCOUNT_ADDRESS1, disableSalt) - const res = await queryDomainEndpoint(req, SignerEndpoint.DOMAIN_QUOTA_STATUS) - expect(res.status).toBe(200) - const resBody: DomainQuotaStatusResponseSuccess = await res.json() - expect(resBody).toEqual({ - success: true, - version: expectedVersion, - status: { disabled: true, counter: 0, timer: 0, now: resBody.status.now }, - }) - }) - - it('Should respond with 400 on missing request fields', async () => { - const badRequest = await quotaRequest(wallet, ACCOUNT_ADDRESS1, quotaSalt) - // @ts-ignore Intentionally deleting required field - delete badRequest.domain.version - const res = await queryDomainEndpoint(badRequest, SignerEndpoint.DOMAIN_QUOTA_STATUS) - expect(res.status).toBe(400) - const resBody: DomainQuotaStatusResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on unknown domain', async () => { - const badRequest = await quotaRequest(wallet, ACCOUNT_ADDRESS1, quotaSalt) - // @ts-ignore UnknownDomain is (intentionally) not a valid domain identifier. - badRequest.domain.name = 'UnknownDomain' - const res = await queryDomainEndpoint(badRequest, SignerEndpoint.DOMAIN_QUOTA_STATUS) - expect(res.status).toBe(400) - const resBody: DomainQuotaStatusResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on bad encoding', async () => { - const badRequest = await quotaRequest(wallet, ACCOUNT_ADDRESS1, quotaSalt) - // @ts-ignore Intentionally not JSON - badRequest.domain = 'Freddy' - const res = await queryDomainEndpoint(badRequest, SignerEndpoint.DOMAIN_QUOTA_STATUS) - expect(res.status).toBe(400) - const resBody: DomainQuotaStatusResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed auth', async () => { - const badRequest = await quotaRequest(wallet, ACCOUNT_ADDRESS1, quotaSalt) - badRequest.domain.salt = defined('badSalt') - const res = await queryDomainEndpoint(badRequest, SignerEndpoint.DOMAIN_QUOTA_STATUS) - expect(res.status).toBe(401) - const resBody: DomainQuotaStatusResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - }) - - describe(`${SignerEndpoint.DOMAIN_SIGN}`, () => { - const signSaltNew = `${signSalt}-${Date.now()}` - let req: DomainRestrictedSignatureRequest - let poprf: ThresholdPoprfClient - beforeAll(async () => { - ;[req, poprf] = await signatureRequest(wallet, ACCOUNT_ADDRESS1, signSaltNew) - }) - it('[Signer configuration test] Should respond with 200 on valid request for new domain', async () => { - const res = await queryDomainEndpoint(req, SignerEndpoint.DOMAIN_SIGN) - expect(res.status).toBe(200) - const resBody: DomainRestrictedSignatureResponseSuccess = await res.json() - expect(resBody).toEqual({ - success: true, - version: expectedVersion, - signature: resBody.signature, - status: { - disabled: false, - counter: 1, - timer: resBody.status.timer, - now: resBody.status.now, - }, - }) - expect(res.headers.get(KEY_VERSION_HEADER)).toEqual(contextSpecificParams.domainsKeyVersion) - poprf.unblindPartialResponse( - // throws if verification fails - Buffer.from(resBody.signature, 'base64') - ) - }) - - it('Should respond with 401 on invalid nonce', async () => { - // Replay exactly the same first request - const res = await queryDomainEndpoint(req, SignerEndpoint.DOMAIN_SIGN) - expect(res.status).toBe(401) - const resBody: DomainRestrictedSignatureResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_NONCE, - status: { - disabled: false, - counter: 1, - timer: resBody.status!.timer, - now: resBody.status!.now, - }, - }) - }) - - it('Should respond with 200 on repeated valid requests with nonce updated', async () => { - // submit identical request with nonce set to 1 - req.options.nonce = defined(1) - req.options.signature = noString - req.options.signature = defined( - await wallet.signTypedData(ACCOUNT_ADDRESS1, domainRestrictedSignatureRequestEIP712(req)) - ) - - // TODO(ODIS 2.0.0 e2e fix) clean up this duplicated logic - const headers: any = { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'ignore', - } - const res = await fetch(ODIS_SIGNER_URL + SignerEndpoint.DOMAIN_SIGN, { - method: 'POST', - headers, - body: JSON.stringify(req), - }) - expect(res.status).toBe(200) - const resBody: DomainRestrictedSignatureResponseSuccess = await res.json() - expect(resBody).toEqual({ - success: true, - version: expectedVersion, - signature: resBody.signature, - status: { - disabled: false, - counter: 2, - timer: resBody.status.timer, - now: resBody.status.now, - }, - }) - poprf.unblindPartialResponse(Buffer.from(resBody.signature, 'base64')) - }) - - it('Should respond with 200 if nonce > domainState', async () => { - const [newReq, _poprf] = await signatureRequest( - wallet, - ACCOUNT_ADDRESS1, - `${signSalt}-${Date.now()}`, - undefined, - 5 - ) - const res = await queryDomainEndpoint(newReq, SignerEndpoint.DOMAIN_SIGN) - expect(res.status).toBe(200) - const resBody: DomainRestrictedSignatureResponseSuccess = await res.json() - expect(resBody).toEqual({ - success: true, - version: expectedVersion, - signature: resBody.signature, - status: { - disabled: false, - counter: 1, // counter gets incremented, not set to nonce value - timer: resBody.status.timer, - now: resBody.status.now, - }, - }) - _poprf.unblindPartialResponse(Buffer.from(resBody.signature, 'base64')) - }) - - it('Should respond with 200 on valid request with key version header', async () => { - const [newReq, _poprf] = await signatureRequest( - wallet, - ACCOUNT_ADDRESS1, - `${signSalt}-${Date.now() + 1}` - ) - const res = await queryDomainEndpoint( - newReq, - SignerEndpoint.DOMAIN_SIGN, - contextSpecificParams.domainsKeyVersion - ) - expect(res.status).toBe(200) - const resBody: DomainRestrictedSignatureResponseSuccess = await res.json() - expect(resBody).toEqual({ - success: true, - version: expectedVersion, - signature: resBody.signature, - status: { - disabled: false, - counter: 1, // counter gets incremented, not set to nonce value - timer: resBody.status.timer, - now: resBody.status.now, - }, - }) - expect(res.headers.get(KEY_VERSION_HEADER)).toEqual(contextSpecificParams.domainsKeyVersion) - _poprf.unblindPartialResponse(Buffer.from(resBody.signature, 'base64')) - }) - - it('Should respond with 400 on missing request fields', async () => { - const [badRequest, _] = await signatureRequest( - wallet, - ACCOUNT_ADDRESS1, - `${signSalt}-${Date.now()}` - ) - // @ts-ignore Intentionally deleting required field - delete badRequest.domain.version - const res = await queryDomainEndpoint(badRequest, SignerEndpoint.DOMAIN_SIGN) - expect(res.status).toBe(400) - const resBody: DomainRestrictedSignatureResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on missing request fields', async () => { - const [badRequest, _] = await signatureRequest( - wallet, - ACCOUNT_ADDRESS1, - `${signSalt}-${Date.now()}` - ) - // @ts-ignore UnknownDomain is (intentionally) not a valid domain identifier. - badRequest.domain.name = 'UnknownDomain' - const res = await queryDomainEndpoint(badRequest, SignerEndpoint.DOMAIN_SIGN) - expect(res.status).toBe(400) - const resBody: DomainRestrictedSignatureResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on bad encoding', async () => { - const [badRequest, _] = await signatureRequest( - wallet, - ACCOUNT_ADDRESS1, - `${signSalt}-${Date.now()}` - ) - // @ts-ignore Intentionally not JSON - badRequest.domain = 'Freddy' - const res = await queryDomainEndpoint(badRequest, SignerEndpoint.DOMAIN_SIGN) - expect(res.status).toBe(400) - const resBody: DomainRestrictedSignatureResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on bad encoding', async () => { - const [badRequest, _] = await signatureRequest( - wallet, - ACCOUNT_ADDRESS1, - `${signSalt}-${Date.now()}` - ) - const res = await queryDomainEndpoint(badRequest, SignerEndpoint.DOMAIN_SIGN, 'a') - expect(res.status).toBe(400) - const resBody: DomainRestrictedSignatureResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_KEY_VERSION_REQUEST, - }) - }) - - it('Should respond with 401 on failed auth', async () => { - const [badRequest, _] = await signatureRequest( - wallet, - ACCOUNT_ADDRESS1, - `${signSalt}-${Date.now()}` - ) - badRequest.domain.salt = defined('badSalt') - const res = await queryDomainEndpoint(badRequest, SignerEndpoint.DOMAIN_SIGN) - expect(res.status).toBe(401) - const resBody: DomainRestrictedSignatureResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 429 on out of quota', async () => { - const salt = `${signSalt}-${Date.now()}` - const noQuotaDomain = authenticatedDomain(ACCOUNT_ADDRESS1, salt, [ - { delay: 0, resetTimer: noBool, batchSize: defined(0), repetitions: defined(0) }, - ]) - const [signReq, _] = await signatureRequest(wallet, ACCOUNT_ADDRESS1, salt, noQuotaDomain) - const res = await queryDomainEndpoint(signReq, SignerEndpoint.DOMAIN_SIGN) - expect(res.status).toBe(429) - const resBody = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.EXCEEDED_QUOTA, - status: { - disabled: false, - counter: 0, - timer: 0, - now: resBody.status!.now, - }, - }) - }) - - it('Should respond with 429 on disabled domain', async () => { - const disableReq = await disableRequest(wallet, ACCOUNT_ADDRESS1, disableSalt) - const disableRes = await queryDomainEndpoint(disableReq, SignerEndpoint.DISABLE_DOMAIN) - expect(disableRes.status).toBe(200) - const [signReq, _] = await signatureRequest( - wallet, - ACCOUNT_ADDRESS1, - `${signSalt}-${Date.now()}`, - disableReq.domain - ) - const res = await queryDomainEndpoint(signReq, SignerEndpoint.DOMAIN_SIGN) - expect(res.status).toBe(429) - const resBody = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.EXCEEDED_QUOTA, - status: { - disabled: true, - counter: 0, - timer: 0, - now: resBody.status!.now, - }, - }) - }) - }) -}) - -async function queryDomainEndpoint( - req: DomainRequest, - endpoint: DomainEndpoint, - keyVersion?: string -): Promise { - const body = JSON.stringify(req) - const headers: any = { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'ignore', - } - - if (keyVersion !== undefined) { - headers[KEY_VERSION_HEADER] = keyVersion - } - - const res = await fetch(ODIS_SIGNER_URL + endpoint, { - method: 'POST', - headers, - body, - }) - return res -} - -// TODO(ODIS 2.0.0 e2e fix) clean up duplicate code -const domainStages = (): SequentialDelayStage[] => [ - { delay: 0, resetTimer: noBool, batchSize: defined(2), repetitions: defined(10) }, -] - -const authenticatedDomain = ( - address: string, - salt: string, - _stages?: SequentialDelayStage[] -): SequentialDelayDomain => ({ - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: _stages ?? domainStages(), - address: defined(address), - salt: defined(salt), -}) - -const quotaRequest = async ( - wallet: LocalWallet, - address: string, - salt: string -): Promise> => { - const req: DomainQuotaStatusRequest = { - type: DomainRequestTypeTag.QUOTA, - domain: authenticatedDomain(address, salt), - options: { - signature: noString, - nonce: noNumber, - }, - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await wallet.signTypedData(address, domainQuotaStatusRequestEIP712(req)) - ) - return req -} - -const disableRequest = async ( - wallet: LocalWallet, - address: string, - salt: string -): Promise> => { - const req: DisableDomainRequest = { - type: DomainRequestTypeTag.DISABLE, - domain: authenticatedDomain(address, salt), - options: { - signature: noString, - nonce: noNumber, - }, - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await wallet.signTypedData(address, disableDomainRequestEIP712(req)) - ) - return req -} - -const signatureRequest = async ( - wallet: LocalWallet, - address: string, - salt: string, - _domain?: SequentialDelayDomain, - _nonce?: number -): Promise<[DomainRestrictedSignatureRequest, ThresholdPoprfClient]> => { - const domain = _domain ?? authenticatedDomain(address, salt) - const thresholdPoprfClient = new ThresholdPoprfClient( - Buffer.from(contextSpecificParams.domainsPubKey, 'base64'), - Buffer.from(contextSpecificParams.domainsPolynomial, 'hex'), - domainHash(domain), - Buffer.from('test message', 'utf8') - ) - - const req: DomainRestrictedSignatureRequest = { - type: DomainRequestTypeTag.SIGN, - domain: domain, - options: { - signature: noString, - nonce: defined(_nonce ?? 0), - }, - blindedMessage: thresholdPoprfClient.blindedMessage.toString('base64'), - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await wallet.signTypedData(address, domainRestrictedSignatureRequestEIP712(req)) - ) - return [req, thresholdPoprfClient] -} diff --git a/packages/phone-number-privacy/signer/test/end-to-end/pnp.test.ts b/packages/phone-number-privacy/signer/test/end-to-end/pnp.test.ts deleted file mode 100644 index 8be4b6f10b..0000000000 --- a/packages/phone-number-privacy/signer/test/end-to-end/pnp.test.ts +++ /dev/null @@ -1,514 +0,0 @@ -import { sleep } from '@celo/base' -import { newKit, StableToken } from '@celo/contractkit' -import { - AuthenticationMethod, - KEY_VERSION_HEADER, - PnpQuotaRequest, - PnpQuotaResponseFailure, - PnpQuotaResponseSuccess, - SignerEndpoint, - SignMessageRequest, - SignMessageResponseFailure, - SignMessageResponseSuccess, - TestUtils, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import threshold_bls from 'blind-threshold-bls' -import { randomBytes } from 'crypto' -import 'isomorphic-fetch' -import { config } from '../../src/config' -import { getBlindedPhoneNumber, getTestParamsForContext } from './utils' - -require('dotenv').config() - -const { - ACCOUNT_ADDRESS1, // zero OdisPayments balance/quota - ACCOUNT_ADDRESS2, // non-zero OdisPayments balance/quota - DEK_PRIVATE_KEY, - DEK_PUBLIC_KEY, - PHONE_NUMBER, - PRIVATE_KEY1, - PRIVATE_KEY2, - PRIVATE_KEY3, -} = TestUtils.Values -const { getPnpQuotaRequest, getPnpRequestAuthorization, getPnpSignRequest } = TestUtils.Utils - -const ODIS_SIGNER_URL = process.env.ODIS_SIGNER_SERVICE_URL -const contextSpecificParams = getTestParamsForContext() - -const kit = newKit(contextSpecificParams.blockchainProviderURL) -kit.addAccount(PRIVATE_KEY1) -kit.addAccount(PRIVATE_KEY2) -kit.addAccount(PRIVATE_KEY3) - -jest.setTimeout(60000) - -const expectedVersion = process.env.DEPLOYED_SIGNER_SERVICE_VERSION! - -describe(`Running against service deployed at ${ODIS_SIGNER_URL}`, () => { - const singleQueryCost = config.quota.queryPriceInCUSD.times(1e18).toString() - - beforeAll(async () => { - const accountsWrapper = await kit.contracts.getAccounts() - if ((await accountsWrapper.getDataEncryptionKey(ACCOUNT_ADDRESS2)) !== DEK_PUBLIC_KEY) { - await accountsWrapper - .setAccountDataEncryptionKey(DEK_PUBLIC_KEY) - .sendAndWaitForReceipt({ from: ACCOUNT_ADDRESS2 }) - } - }) - - it('Service is deployed at correct version', async () => { - const response = await fetch(ODIS_SIGNER_URL + SignerEndpoint.STATUS, { - method: 'GET', - }) - expect(response.status).toBe(200) - const body = await response.json() - // This checks against local package.json version, change if necessary - expect(body.version).toBe(expectedVersion) - }) - - describe(`${SignerEndpoint.PNP_QUOTA}`, () => { - it('Should respond with 200 on valid request', async () => { - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS1) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await queryPnpQuotaEndpoint(req, authorization) - expect(res.status).toBe(200) - const resBody: PnpQuotaResponseSuccess = await res.json() - expect(resBody).toEqual({ - success: true, - version: expectedVersion, - performedQueryCount: 0, - totalQuota: 0, - warnings: [], - }) - }) - - it('Should respond with 200 on valid request when authenticated with DEK', async () => { - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS2, AuthenticationMethod.ENCRYPTION_KEY) - const authorization = getPnpRequestAuthorization(req, DEK_PRIVATE_KEY) - const res = await queryPnpQuotaEndpoint(req, authorization) - expect(res.status).toBe(200) - const resBody: PnpQuotaResponseSuccess = await res.json() - expect(resBody).toEqual({ - success: true, - version: expectedVersion, - performedQueryCount: resBody.performedQueryCount, - totalQuota: resBody.totalQuota, - warnings: [], - }) - expect(resBody.totalQuota).toBeGreaterThan(0) - }) - - it('Should respond with 200 and more quota after payment sent to OdisPayments.sol', async () => { - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS2) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY2) - const res = await queryPnpQuotaEndpoint(req, authorization) - expect(res.status).toBe(200) - - const resBody: PnpQuotaResponseSuccess = await res.json() - - await sendCUSDToOdisPayments(singleQueryCost, ACCOUNT_ADDRESS2, ACCOUNT_ADDRESS2) - - const res2 = await queryPnpQuotaEndpoint(req, authorization) - expect(res2.status).toBe(200) - const res2Body: PnpQuotaResponseSuccess = await res2.json() - expect(res2Body).toEqual({ - success: true, - version: expectedVersion, - performedQueryCount: resBody.performedQueryCount, - totalQuota: resBody.totalQuota, - warnings: [], - }) - - await sleep(5 * 1000) // sleep for cache ttl - - const res3 = await queryPnpQuotaEndpoint(req, authorization) - expect(res3.status).toBe(200) - const res3Body: PnpQuotaResponseSuccess = await res3.json() - expect(res3Body).toEqual({ - success: true, - version: expectedVersion, - performedQueryCount: resBody.performedQueryCount, - totalQuota: resBody.totalQuota + 1, // req2 updated the cache, but stale value was returned - warnings: [], - }) - }) - - it('Should respond with 400 on missing request fields', async () => { - const badRequest = getPnpQuotaRequest(ACCOUNT_ADDRESS1) - // @ts-ignore Intentionally deleting required field - delete badRequest.account - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await queryPnpQuotaEndpoint(badRequest, authorization) - expect(res.status).toBe(400) - const resBody: PnpQuotaResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed WALLET_KEY auth', async () => { - const badRequest = getPnpQuotaRequest(ACCOUNT_ADDRESS2, AuthenticationMethod.WALLET_KEY) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await queryPnpQuotaEndpoint(badRequest, authorization) - expect(res.status).toBe(401) - const resBody: PnpQuotaResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 401 on failed DEK auth when DEK is set for account', async () => { - const badRequest = getPnpQuotaRequest(ACCOUNT_ADDRESS2, AuthenticationMethod.ENCRYPTION_KEY) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY2) - const res = await queryPnpQuotaEndpoint(badRequest, authorization) - expect(res.status).toBe(401) - const resBody: PnpQuotaResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 401 on failed DEK auth when DEK is not set for account', async () => { - const badRequest = getPnpQuotaRequest(ACCOUNT_ADDRESS1, AuthenticationMethod.ENCRYPTION_KEY) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await queryPnpQuotaEndpoint(badRequest, authorization) - expect(res.status).toBe(401) - const resBody: PnpQuotaResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - }) - - describe(`${SignerEndpoint.PNP_SIGN}`, () => { - describe('success cases', () => { - let startingPerformedQueryCount: number - - beforeEach(async () => { - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS2) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY2) - const res = await queryPnpQuotaEndpoint(req, authorization) - expect(res.status).toBe(200) - const resBody: PnpQuotaResponseSuccess = await res.json() - startingPerformedQueryCount = resBody.performedQueryCount - }) - - it('[Signer configuration test] Should respond with 200 on valid request', async () => { - const blindedMessage = getBlindedPhoneNumber(PHONE_NUMBER, randomBytes(32)) - const req = getPnpSignRequest( - ACCOUNT_ADDRESS2, - blindedMessage, - AuthenticationMethod.WALLET_KEY - ) - await sendCUSDToOdisPayments(singleQueryCost, ACCOUNT_ADDRESS2, ACCOUNT_ADDRESS2) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY2) - const res = await queryPnpSignEndpoint(req, authorization) - expect(res.status).toBe(200) - const resBody: SignMessageResponseSuccess = await res.json() - expect(resBody).toEqual({ - success: true, - version: expectedVersion, - signature: resBody.signature, - performedQueryCount: startingPerformedQueryCount + 1, - totalQuota: resBody.totalQuota, - warnings: [], - }) - expect(res.headers.get(KEY_VERSION_HEADER)).toEqual(contextSpecificParams.pnpKeyVersion) - expect( - threshold_bls.partialVerifyBlindSignature( - Buffer.from(contextSpecificParams.pnpPolynomial, 'hex'), - Buffer.from(blindedMessage, 'base64'), - Buffer.from(resBody.signature, 'base64') - ) - ) - }) - - it(`Should respond with 200 on valid request with key version ${contextSpecificParams.pnpKeyVersion}`, async () => { - // This value can also be modified but needs to be manually inspected in the signer logs - // (on staging) since a valid key version that does not exist in the keystore - // will default to the secretName stored in `KEYSTORE_AZURE_SECRET_NAME` - const keyVersion = contextSpecificParams.pnpKeyVersion - const blindedMessage = getBlindedPhoneNumber(PHONE_NUMBER, randomBytes(32)) - const req = getPnpSignRequest( - ACCOUNT_ADDRESS2, - blindedMessage, - AuthenticationMethod.WALLET_KEY - ) - await sendCUSDToOdisPayments(singleQueryCost, ACCOUNT_ADDRESS2, ACCOUNT_ADDRESS2) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY2) - const res = await queryPnpSignEndpoint(req, authorization, keyVersion) - expect(res.status).toBe(200) - const resBody: SignMessageResponseSuccess = await res.json() - expect(resBody).toEqual({ - success: true, - version: expectedVersion, - signature: resBody.signature, - performedQueryCount: startingPerformedQueryCount + 1, - totalQuota: resBody.totalQuota, - warnings: [], - }) - expect(res.headers.get(KEY_VERSION_HEADER)).toEqual(keyVersion) - expect( - threshold_bls.partialVerifyBlindSignature( - Buffer.from(contextSpecificParams.pnpPolynomial, 'hex'), - Buffer.from(blindedMessage, 'base64'), - Buffer.from(resBody.signature, 'base64') - ) - ) - }) - - it('Should respond with 200 and warning on repeated valid requests', async () => { - await sendCUSDToOdisPayments(singleQueryCost, ACCOUNT_ADDRESS2, ACCOUNT_ADDRESS2) - const blindedMessage = getBlindedPhoneNumber(PHONE_NUMBER, randomBytes(32)) - const req = getPnpSignRequest( - ACCOUNT_ADDRESS2, - blindedMessage, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY2) - const res = await queryPnpSignEndpoint(req, authorization) - expect(res.status).toBe(200) - const resBody: SignMessageResponseSuccess = await res.json() - expect(resBody).toEqual({ - success: true, - version: expectedVersion, - signature: resBody.signature, - performedQueryCount: startingPerformedQueryCount + 1, - totalQuota: resBody.totalQuota, - warnings: [], - }) - expect(res.headers.get(KEY_VERSION_HEADER)).toEqual(contextSpecificParams.pnpKeyVersion) - expect( - threshold_bls.partialVerifyBlindSignature( - Buffer.from(contextSpecificParams.pnpPolynomial, 'hex'), - Buffer.from(blindedMessage, 'base64'), - Buffer.from(resBody.signature, 'base64') - ) - ) - - await sleep(5 * 1000) // sleep for cache ttl - - const res2 = await queryPnpSignEndpoint(req, authorization) - expect(res2.status).toBe(200) - const res2Body: SignMessageResponseSuccess = await res2.json() - expect(res2Body).toEqual({ - success: true, - version: expectedVersion, - signature: resBody.signature, - performedQueryCount: resBody.performedQueryCount, // Not incremented - totalQuota: resBody.totalQuota + 1, // prev request updated cache - warnings: [WarningMessage.DUPLICATE_REQUEST_TO_GET_PARTIAL_SIG], - }) - }) - }) - - describe('failure cases', () => { - const blindedMessage = getBlindedPhoneNumber(PHONE_NUMBER, randomBytes(32)) - - it('Should respond with 400 on missing request fields', async () => { - const badRequest = getPnpSignRequest( - ACCOUNT_ADDRESS2, - blindedMessage, - AuthenticationMethod.WALLET_KEY - ) - // @ts-ignore Intentionally deleting required field - delete badRequest.blindedQueryPhoneNumber - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await queryPnpSignEndpoint(badRequest, authorization) - expect(res.status).toBe(400) - const resBody: SignMessageResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on on invalid key version', async () => { - const badRequest = getPnpSignRequest( - ACCOUNT_ADDRESS2, - blindedMessage, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await queryPnpSignEndpoint(badRequest, authorization, 'asd') - expect(res.status).toBe(400) - const resBody: SignMessageResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_KEY_VERSION_REQUEST, - }) - }) - - it('Should respond with 400 on on invalid blinded message', async () => { - const badRequest = getPnpSignRequest( - ACCOUNT_ADDRESS2, - PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await queryPnpSignEndpoint(badRequest, authorization) - expect(res.status).toBe(400) - const resBody: SignMessageResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on invalid address', async () => { - const badRequest = getPnpSignRequest( - '0xnotanaddress', - blindedMessage, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await queryPnpSignEndpoint(badRequest, authorization) - expect(res.status).toBe(400) - const resBody: SignMessageResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed WALLET_KEY auth', async () => { - const badRequest = getPnpSignRequest( - ACCOUNT_ADDRESS2, - blindedMessage, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await queryPnpSignEndpoint(badRequest, authorization) - expect(res.status).toBe(401) - const resBody: SignMessageResponseFailure = await res.json() - expect(resBody).toEqual({ - // TODO test if toStrictEqual works after fixing sendFailure - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 401 on failed DEK auth when DEK is set for account', async () => { - const badRequest = getPnpSignRequest( - ACCOUNT_ADDRESS2, - blindedMessage, - AuthenticationMethod.ENCRYPTION_KEY - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY2) - const res = await queryPnpSignEndpoint(badRequest, authorization) - expect(res.status).toBe(401) - const resBody: SignMessageResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 401 on failed DEK auth when DEK is not set for account', async () => { - const badRequest = getPnpSignRequest( - ACCOUNT_ADDRESS1, - blindedMessage, - AuthenticationMethod.ENCRYPTION_KEY - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await queryPnpSignEndpoint(badRequest, authorization) - expect(res.status).toBe(401) - const resBody: SignMessageResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 403 on out of quota', async () => { - const quotaReq = getPnpQuotaRequest(ACCOUNT_ADDRESS1) - const quotaAuthorization = getPnpRequestAuthorization(quotaReq, PRIVATE_KEY1) - const quotaRes = await queryPnpQuotaEndpoint(quotaReq, quotaAuthorization) - expect(quotaRes.status).toBe(200) - const quotaResBody: PnpQuotaResponseSuccess = await quotaRes.json() - // Sanity check - expect(quotaResBody.performedQueryCount).toEqual(quotaResBody.totalQuota) - - const req = getPnpSignRequest(ACCOUNT_ADDRESS1, blindedMessage) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await queryPnpSignEndpoint(req, authorization) - expect(res.status).toBe(403) - const resBody: SignMessageResponseFailure = await res.json() - expect(resBody).toEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.EXCEEDED_QUOTA, - totalQuota: quotaResBody.totalQuota, - performedQueryCount: quotaResBody.performedQueryCount, - }) - }) - }) - }) -}) - -async function queryPnpQuotaEndpoint( - req: PnpQuotaRequest, - authorization: string -): Promise { - const body = JSON.stringify(req) - return fetch(ODIS_SIGNER_URL + SignerEndpoint.PNP_QUOTA, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: authorization, - }, - body, - }) -} - -async function queryPnpSignEndpoint( - req: SignMessageRequest, - authorization: string, - keyVersion?: string -): Promise { - const body = JSON.stringify(req) - const headers: any = { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: authorization, - } - if (keyVersion !== undefined) { - headers[KEY_VERSION_HEADER] = keyVersion - } - const res = await fetch(ODIS_SIGNER_URL + SignerEndpoint.PNP_SIGN, { - method: 'POST', - headers, - body, - }) - return res -} - -async function sendCUSDToOdisPayments( - amountInWei: string | number, - recipient: string, - sender: string -) { - const stableToken = await kit.contracts.getStableToken(StableToken.cUSD) - const odisPayments = await kit.contracts.getOdisPayments() - await stableToken - .approve(odisPayments.address, amountInWei) - .sendAndWaitForReceipt({ from: sender }) - await odisPayments.payInCUSD(recipient, amountInWei).sendAndWaitForReceipt({ from: sender }) -} diff --git a/packages/phone-number-privacy/signer/test/end-to-end/utils.ts b/packages/phone-number-privacy/signer/test/end-to-end/utils.ts deleted file mode 100644 index 59b9972715..0000000000 --- a/packages/phone-number-privacy/signer/test/end-to-end/utils.ts +++ /dev/null @@ -1,53 +0,0 @@ -import threshold_bls from 'blind-threshold-bls' - -require('dotenv').config() - -export function getBlindedPhoneNumber(phoneNumber: string, blindingFactor: Buffer): string { - const blindedPhoneNumber = threshold_bls.blind(Buffer.from(phoneNumber), blindingFactor).message - return Buffer.from(blindedPhoneNumber).toString('base64') -} - -export type E2ETestParams = { - blockchainProviderURL: string - pnpPolynomial: string - pnpKeyVersion: string - domainsPolynomial: string - domainsPubKey: string - domainsKeyVersion: string -} - -// Once context-specific params are located in the common package, -// consider using that instead of redefining the specifics here. -export function getTestParamsForContext(): E2ETestParams { - switch (process.env.CONTEXT_NAME) { - case 'staging': - return { - blockchainProviderURL: process.env.STAGING_ODIS_BLOCKCHAIN_PROVIDER!, - pnpPolynomial: process.env.STAGING_POLYNOMIAL!, - pnpKeyVersion: process.env.ODIS_PNP_TEST_KEY_VERSION!, - domainsPolynomial: process.env.STAGING_POLYNOMIAL!, - domainsPubKey: process.env.STAGING_DOMAINS_PUBKEY!, - domainsKeyVersion: process.env.ODIS_DOMAINS_TEST_KEY_VERSION!, - } - case 'alfajores': - return { - blockchainProviderURL: process.env.ALFAJORES_ODIS_BLOCKCHAIN_PROVIDER!, - pnpPolynomial: process.env.ALFAJORES_PHONE_NUMBER_PRIVACY_POLYNOMIAL!, - pnpKeyVersion: process.env.ODIS_PNP_TEST_KEY_VERSION!, - domainsPolynomial: process.env.ALFAJORES_DOMAINS_POLYNOMIAL!, - domainsPubKey: process.env.ALFAJORES_DOMAINS_PUBKEY!, - domainsKeyVersion: process.env.ODIS_DOMAINS_TEST_KEY_VERSION!, - } - case 'mainnet': - return { - blockchainProviderURL: process.env.MAINNET_ODIS_BLOCKCHAIN_PROVIDER!, - pnpPolynomial: process.env.MAINNET_PHONE_NUMBER_PRIVACY_POLYNOMIAL!, - pnpKeyVersion: process.env.ODIS_PNP_TEST_KEY_VERSION!, - domainsPolynomial: process.env.MAINNET_DOMAINS_POLYNOMIAL!, - domainsPubKey: process.env.MAINNET_DOMAINS_PUBKEY!, - domainsKeyVersion: process.env.ODIS_DOMAINS_TEST_KEY_VERSION!, - } - default: - throw new Error(`No parameter settings stored for context: ${process.env.CONTEXT_NAME}`) - } -} diff --git a/packages/phone-number-privacy/signer/test/integration/domain.test.ts b/packages/phone-number-privacy/signer/test/integration/domain.test.ts deleted file mode 100644 index b9141f14fa..0000000000 --- a/packages/phone-number-privacy/signer/test/integration/domain.test.ts +++ /dev/null @@ -1,1033 +0,0 @@ -import { - DisableDomainRequest, - disableDomainRequestEIP712, - DisableDomainResponse, - DisableDomainResponseSuccess, - domainHash, - DomainIdentifiers, - DomainQuotaStatusRequest, - domainQuotaStatusRequestEIP712, - DomainQuotaStatusResponse, - DomainRequestTypeTag, - DomainRestrictedSignatureRequest, - domainRestrictedSignatureRequestEIP712, - DomainRestrictedSignatureResponse, - ErrorMessage, - genSessionID, - KEY_VERSION_HEADER, - rootLogger, - SequentialDelayDomain, - SequentialDelayStage, - SignerEndpoint, - TestUtils, - ThresholdPoprfClient, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { defined, noBool, noNumber, noString } from '@celo/utils/lib/sign-typed-data-utils' -import { LocalWallet } from '@celo/wallet-local' -import { Knex } from 'knex' -import request from 'supertest' -import { initDatabase } from '../../src/common/database/database' -import { countAndThrowDBError } from '../../src/common/database/utils' -import { - createEmptyDomainStateRecord, - getDomainStateRecord, -} from '../../src/common/database/wrappers/domain-state' -import { initKeyProvider } from '../../src/common/key-management/key-provider' -import { KeyProvider } from '../../src/common/key-management/key-provider-base' -import { config, getSignerVersion, SupportedDatabase, SupportedKeystore } from '../../src/config' -import { startSigner } from '../../src/server' - -jest.setTimeout(20000) - -describe('domain', () => { - const wallet = new LocalWallet() - wallet.addAccount('0x00000000000000000000000000000000000000000000000000000000deadbeef') - const walletAddress = wallet.getAccounts()[0]! - - const expectedVersion = getSignerVersion() - - const domainStages = (): SequentialDelayStage[] => [ - { delay: 0, resetTimer: noBool, batchSize: defined(2), repetitions: defined(10) }, - ] - - const authenticatedDomain = (_stages?: SequentialDelayStage[]): SequentialDelayDomain => ({ - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: _stages ?? domainStages(), - address: defined(walletAddress), - salt: defined('himalayanPink'), - }) - - const signatureRequest = async ( - _domain?: SequentialDelayDomain, - _nonce?: number, - keyVersion: number = config.keystore.keys.domains.latest - ): Promise<[DomainRestrictedSignatureRequest, ThresholdPoprfClient]> => { - const domain = _domain ?? authenticatedDomain() - const thresholdPoprfClient = new ThresholdPoprfClient( - Buffer.from(TestUtils.Values.DOMAINS_THRESHOLD_DEV_PUBKEYS[keyVersion - 1], 'base64'), - Buffer.from(TestUtils.Values.DOMAINS_THRESHOLD_DEV_POLYNOMIALS[keyVersion - 1], 'hex'), - domainHash(domain), - Buffer.from('test message', 'utf8') - ) - - const req: DomainRestrictedSignatureRequest = { - type: DomainRequestTypeTag.SIGN, - domain: domain, - options: { - signature: noString, - nonce: defined(_nonce ?? 0), - }, - blindedMessage: thresholdPoprfClient.blindedMessage.toString('base64'), - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await wallet.signTypedData(walletAddress, domainRestrictedSignatureRequestEIP712(req)) - ) - return [req, thresholdPoprfClient] - } - - const quotaRequest = async (): Promise> => { - const req: DomainQuotaStatusRequest = { - type: DomainRequestTypeTag.QUOTA, - domain: authenticatedDomain(), - options: { - signature: noString, - nonce: noNumber, - }, - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await wallet.signTypedData(walletAddress, domainQuotaStatusRequestEIP712(req)) - ) - return req - } - - // Build and sign an example disable domain request. - const disableRequest = async (): Promise> => { - const req: DisableDomainRequest = { - type: DomainRequestTypeTag.DISABLE, - domain: authenticatedDomain(), - options: { - signature: noString, - nonce: noNumber, - }, - sessionID: defined(genSessionID()), - } - req.options.signature = defined( - await wallet.signTypedData(walletAddress, disableDomainRequestEIP712(req)) - ) - return req - } - - let keyProvider: KeyProvider - let app: any - let db: Knex - - // create deep copy - const _config: typeof config = JSON.parse(JSON.stringify(config)) - _config.db.type = SupportedDatabase.Sqlite - _config.keystore.type = SupportedKeystore.MOCK_SECRET_MANAGER - _config.api.domains.enabled = true - - beforeAll(async () => { - keyProvider = await initKeyProvider(_config) - }) - - beforeEach(async () => { - // Create a new in-memory database for each test. - db = await initDatabase(_config) - app = startSigner(_config, db, keyProvider) - }) - - afterEach(async () => { - // Close and destroy the in-memory database. - // Note: If tests start to be too slow, this could be replaced with more complicated logic to - // reset the database state without destroying and recreating it for each test. - - await db?.destroy() - }) - - describe(`${SignerEndpoint.STATUS}`, () => { - it('Should return 200 and correct version', async () => { - const res = await request(app).get(SignerEndpoint.STATUS) - expect(res.status).toBe(200) - expect(res.body.version).toBe(expectedVersion) - }) - }) - - describe(`${SignerEndpoint.DISABLE_DOMAIN}`, () => { - it('Should respond with 200 on valid request', async () => { - const res = await request(app) - .post(SignerEndpoint.DISABLE_DOMAIN) - .send(await disableRequest()) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - status: { - disabled: true, - counter: 0, - timer: 0, - now: res.body.status.now, - }, - }) - }) - - it('Should respond with 200 on repeated valid requests', async () => { - const req = await disableRequest() - const res1 = await request(app).post(SignerEndpoint.DISABLE_DOMAIN).send(req) - expect(res1.status).toBe(200) - const expectedResponse: DisableDomainResponseSuccess = { - success: true, - version: res1.body.version, - status: { - disabled: true, - counter: 0, - timer: 0, - now: res1.body.status.now, - }, - } - expect(res1.body).toStrictEqual(expectedResponse) - const res2 = await request(app).post(SignerEndpoint.DISABLE_DOMAIN).send(req) - expect(res2.status).toBe(200) - // Avoid flakiness due to mismatching times between res1 & res2 - expectedResponse.status.now = res2.body.status.now - expect(res2.body).toStrictEqual(expectedResponse) - }) - - it('Should respond with 200 on extra request fields', async () => { - const req = await disableRequest() - // @ts-ignore Intentionally adding an extra field to the request type - req.options.extraField = noString - - const res = await request(app).post(SignerEndpoint.DISABLE_DOMAIN).send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - status: { - disabled: true, - counter: 0, - timer: 0, - now: res.body.status.now, - }, - }) - }) - - it('Should respond with 400 on missing request fields', async () => { - const badRequest = await disableRequest() - // @ts-ignore Intentionally deleting required field - delete badRequest.domain.version - - const res = await request(app).post(SignerEndpoint.DISABLE_DOMAIN).send(badRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on unknown domain', async () => { - // Create a request with an invalid domain identifier. - const unknownRequest = await disableRequest() - // @ts-ignore UnknownDomain is (intentionally) not a valid domain identifier. - unknownRequest.domain.name = 'UnknownDomain' - - const res = await request(app).post(SignerEndpoint.DISABLE_DOMAIN).send(unknownRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on bad encoding', async () => { - const badRequest1 = await disableRequest() - // @ts-ignore Intentionally not JSON - badRequest1.domain = 'Freddy' - - const res1 = await request(app).post(SignerEndpoint.DISABLE_DOMAIN).send(badRequest1) - - expect(res1.status).toBe(400) - expect(res1.body).toStrictEqual({ - success: false, - version: res1.body.version, - error: WarningMessage.INVALID_INPUT, - }) - - const badRequest2 = '' - - const res2 = await request(app).post(SignerEndpoint.DISABLE_DOMAIN).send(badRequest2) - - expect(res2.status).toBe(400) - expect(res2.body).toStrictEqual({ - success: false, - version: res2.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed auth', async () => { - // Create a manipulated request, which will have a bad signature. - const badRequest = await disableRequest() - badRequest.domain.salt = defined('badSalt') - - const res = await request(app).post(SignerEndpoint.DISABLE_DOMAIN).send(badRequest) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 503 on disabled api', async () => { - const configWithApiDisabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithApiDisabled.api.domains.enabled = false - const appWithApiDisabled = startSigner(configWithApiDisabled, db, keyProvider) - - const req = await disableRequest() - - const res = await request(appWithApiDisabled).post(SignerEndpoint.DISABLE_DOMAIN).send(req) - - expect(res.status).toBe(503) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - - describe('functionality in case of errors', () => { - it('Should respond with 500 on DB insertDomainStateRecord failure', async () => { - const req = await disableRequest() - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/domain-state'), - 'insertDomainStateRecord' - ) - .mockImplementationOnce(() => { - // Handle errors in the same way as in insertDomainStateRecord - countAndThrowDBError( - new Error(), - rootLogger(_config.serviceName), - ErrorMessage.DATABASE_INSERT_FAILURE - ) - }) - const res = await request(app).post(SignerEndpoint.DISABLE_DOMAIN).send(req) - spy.mockRestore() - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.DATABASE_INSERT_FAILURE, - }) - expect(await getDomainStateRecord(db, req.domain, rootLogger(_config.serviceName))).toBe( - null - ) - }) - - it('Should respond with 500 on signer timeout', async () => { - const testTimeoutMS = 0 - const delay = 200 - - const configWithShortTimeout = JSON.parse(JSON.stringify(_config)) - configWithShortTimeout.timeout = testTimeoutMS - const appWithShortTimeout = startSigner(configWithShortTimeout, db, keyProvider) - - const req = await disableRequest() - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/domain-state'), - 'getDomainStateRecord' - ) - .mockImplementationOnce(async () => { - await new Promise((resolve) => setTimeout(resolve, testTimeoutMS + delay)) - return null - }) - - const res = await request(appWithShortTimeout).post(SignerEndpoint.DISABLE_DOMAIN).send(req) - spy.mockRestore() - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - error: ErrorMessage.TIMEOUT_FROM_SIGNER, - version: expectedVersion, - }) - }) - }) - }) - - describe(`${SignerEndpoint.DOMAIN_QUOTA_STATUS}`, () => { - it('Should respond with 200 on valid request', async () => { - const res = await request(app) - .post(SignerEndpoint.DOMAIN_QUOTA_STATUS) - .send(await quotaRequest()) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - status: { disabled: false, counter: 0, timer: 0, now: res.body.status.now }, - }) - }) - - it('Should respond with 200 on repeated valid requests', async () => { - const res1 = await request(app) - .post(SignerEndpoint.DOMAIN_QUOTA_STATUS) - .send(await quotaRequest()) - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual({ - success: true, - version: res1.body.version, - status: { disabled: false, counter: 0, timer: 0, now: res1.body.status.now }, - }) - - const res2 = await request(app) - .post(SignerEndpoint.DOMAIN_QUOTA_STATUS) - .send(await quotaRequest()) - expect(res2.status).toBe(200) - expect(res2.body).toStrictEqual({ - success: true, - version: res2.body.version, - status: { disabled: false, counter: 0, timer: 0, now: res2.body.status.now }, - }) - }) - - it('Should respond with 200 on extra request fields', async () => { - const req = await quotaRequest() - // @ts-ignore Intentionally adding an extra field to the request type - req.options.extraField = noString - - const res = await request(app).post(SignerEndpoint.DOMAIN_QUOTA_STATUS).send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - status: { disabled: false, counter: 0, timer: 0, now: res.body.status.now }, - }) - }) - - it('Should respond with 400 on missing request fields', async () => { - const badRequest = await quotaRequest() - // @ts-ignore Intentionally deleting required field - delete badRequest.domain.version - - const res = await request(app).post(SignerEndpoint.DOMAIN_QUOTA_STATUS).send(badRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on unknown domain', async () => { - // Create a request with an invalid domain identifier. - const unknownRequest = await quotaRequest() - // @ts-ignore UnknownDomain is (intentionally) not a valid domain identifier. - unknownRequest.domain.name = 'UnknownDomain' - - const res = await request(app).post(SignerEndpoint.DOMAIN_QUOTA_STATUS).send(unknownRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on bad encoding', async () => { - const badRequest1 = await quotaRequest() - // @ts-ignore Intentionally not JSON - badRequest1.domain = 'Freddy' - - const res1 = await request(app).post(SignerEndpoint.DOMAIN_QUOTA_STATUS).send(badRequest1) - - expect(res1.status).toBe(400) - expect(res1.body).toStrictEqual({ - success: false, - version: res1.body.version, - error: WarningMessage.INVALID_INPUT, - }) - - const badRequest2 = '' - - const res2 = await request(app).post(SignerEndpoint.DOMAIN_QUOTA_STATUS).send(badRequest2) - - expect(res2.status).toBe(400) - expect(res2.body).toStrictEqual({ - success: false, - version: res2.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed auth', async () => { - // Create a manipulated request, which will have a bad signature. - const badRequest = await quotaRequest() - badRequest.domain.salt = defined('badSalt') - - const res = await request(app).post(SignerEndpoint.DOMAIN_QUOTA_STATUS).send(badRequest) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 503 on disabled api', async () => { - const configWithApiDisabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithApiDisabled.api.domains.enabled = false - const appWithApiDisabled = startSigner(configWithApiDisabled, db, keyProvider) - - const req = await quotaRequest() - - const res = await request(appWithApiDisabled) - .post(SignerEndpoint.DOMAIN_QUOTA_STATUS) - .send(req) - - expect(res.status).toBe(503) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - - describe('functionality in case of errors', () => { - it('Should respond with 500 on DB getDomainStateRecordOrEmpty failure', async () => { - const req = await quotaRequest() - // Mocking getDomainStateRecord directly but requiring the real version of - // getDomainStateRecordOrEmpty does not easily work, - // which is why we mock the outer call here & use the countAndThrowDBError - // helper to get as close as possible to testing a real error. - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/domain-state'), - 'getDomainStateRecordOrEmpty' - ) - .mockImplementationOnce(() => { - countAndThrowDBError( - new Error(), - rootLogger(_config.serviceName), - ErrorMessage.DATABASE_GET_FAILURE - ) - }) - const res = await request(app).post(SignerEndpoint.DOMAIN_QUOTA_STATUS).send(req) - spy.mockRestore() - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.DATABASE_GET_FAILURE, - }) - expect(await getDomainStateRecord(db, req.domain, rootLogger(_config.serviceName))).toBe( - null - ) - }) - }) - - it('Should respond with 500 on signer timeout', async () => { - const testTimeoutMS = 0 - const delay = 200 - - const configWithShortTimeout = JSON.parse(JSON.stringify(_config)) - configWithShortTimeout.timeout = testTimeoutMS - const appWithShortTimeout = startSigner(configWithShortTimeout, db, keyProvider) - - const req = await quotaRequest() - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/domain-state'), - 'getDomainStateRecordOrEmpty' - ) - .mockImplementationOnce(async () => { - await new Promise((resolve) => setTimeout(resolve, testTimeoutMS + delay)) - return createEmptyDomainStateRecord(req.domain) - }) - - const res = await request(appWithShortTimeout) - .post(SignerEndpoint.DOMAIN_QUOTA_STATUS) - .send(req) - - spy.mockRestore() - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - error: ErrorMessage.TIMEOUT_FROM_SIGNER, - version: expectedVersion, - }) - // Allow time for non-killed processes to finish - await new Promise((resolve) => setTimeout(resolve, delay)) - }) - }) - - describe(`${SignerEndpoint.DOMAIN_SIGN}`, () => { - const expectedEvals: string[] = [ - 'AQAAALSWngdNIqyApv+AGj50OJxj9fSFPjvGlNZ+oAMykmgfVZd0o59MugofDPrBrUm9AFrr2uPXxKwL6PR2Uo3ch2jfOhRBE9amUTQV9U2gV8b1fFy2uFqnaT6ahV/GE956Aa4n8hiyRD36YL62YELtmGnNo4ODMl98ovirR6BoWp0yOhm42vq2SVRh3O69GYmHAd35Q/jYH9cOXnpNyUf1Pw4WmcbsTc+kwVe+226QJYMGtqafMIFR2AGnTiZji5SOAM7TTCDfZWKj+vtvrlFs3nSRI7AKFBzyx6KIyboljHvtBjhA1EGEzrqEJHLLU+iFASY3vqctLoONWcn6t8puaT5g4bmL3WqHiP+pF/0paLrHyQlxt3NJBcgWXv4GWMh7APDNFXpQ9O/skdlBPED433vMj7ZjXnybkdq7LFuMOua5rY8MEuTtoWizBtoErzBnADb5kWQCYgog94pCuYOYxCoK/+cl2DxVVnt0tarkG4mGK2BY+N6cwHhhYppED3GJAP70+R/nrjWhTp2xwKOd/uJByi/9ORHU16USrVsgka+LrGl/fy3P6BEtoK7cu6JfAXcx54Ojo0eUVsD5W6iHfrgllFk3jSgAvWUJ3I3IG8pTPuKX5eO6c9yL4/PVDY4/AAEkyf5vTG5f5dRd9akptsnVz2dmegSTAcj4md0gDugXzLEitduXF5lsqH6BFo9/AWDTgx2JzBSnSr8HSgSWk0ZKni2UIl1F1Eyb+CN45+5TQDiDv1fsl/0tumMikom1AA==', - 'AQAAAFd54JZz5xv1zf7+U8ZpjfLQQ4kZr5jl8R0/nWUqTcQyiO25awk09nh3elLvd6VkAOxnY0oiHASh7uzDJsi/XuDBJrp1oKoZ85eoCP90/RzGTuGDsHEmKtgDk/lAesQvAeGPjrhyjVleFcdnFq+czoT3aoOrYhdAin2lGU6nWAehbJwUyj5uvI+uEfcvk0KGAVbhZzC1L7jjGaLj+3SmhMfP71kqS5DeaSuyzu0byXum548HT1NRoO96icdfDtUBAfSw/FDzGfrFYf6WdUAObehnGt49AMpkVtGaMOsnETPL/nlbMizK4vMas+NlwyqgAZdCQaGX/FixHYpTDJ6NlBvWHwryoSJFse9XVLg5OizlEYh2ASLxFsZKqMCs7c6TAOYhwSWBeIkJCb0PQBsEMk2/vvnTY5RDAcBYW8aJ118IX/hcO9gO+lLathT+CKlDADGMQTVn1wqFapYZ677Gcsb4JDhUOaQjJCYu7JXlcyhLOe3/0AwI1LjI+ClA+TupADL+qQWTxyfcCfBb9XdcD6klzzFNs0aUfEwgjDyBUVbGrMBSQyE8ErDJYII7j1J4AeacepfWb54q2SLFBIIaoQiWhgeh527QOVbDBYQy0KutYBxEdp/RmJ3vIJL/u/PBAB/5gedsqPzRxIH3yTYoHX5U0bOL6XTmZVNaNZ9rnXn58IXiWORNudbwdA4DGZMOAPUtu1ze8sTUE/PsjNzVPNwhwJz8cQbt4tGC3luRUctkx26K+nZgn8GkCQV9AtByAQ==', - 'AQAAADri1djYjhPpolB6aRwD6ptKRz4EGNAYWba0TYfM/TgQxeoSTupAfJLIJdYEWAEdATyCz9VWW7lC1InwUUCq7vARCWClyAoBQ8LdMAi9bYFy4MrEj+urzTmUgmZL1r39AJOhT9H+SuGv1uBET1Hv49aWZReTo0NhK4U6Oh6y8tHou+P3LC155ZZHLRrmcyGDARnOhBs25CHMjrvkLwcLsJNnK7Y0QXO4/6YEVTBBcsN+F/BGLgtP5GaiPdtDXuYEAFhZW+a0pZIlUzaYZaiXFMQ6pJJbCsMJTK+khfWBSAFuVVkG2wIKTGiTqOkw+o8SAeooTBoO0NJJZcpP+jY++zRziX+X7fyixmBlcStbmVU4gwA1kG/4kvEsrIh+kEygAWvxw/2JZcIDZRRAkhHu+uZwSflSwFFW8omtI36t7YmYmOMpXxTHFNdJyh2mMS29AP7dzScfrKa4NObq/UN85PjIvBTR5otWCFrsT0gNSDnEiGO6cXFIHMexyPRTLYSpAVJra/z283B8DjejVN1qyQFRi9upU5M1vxVLJo5y48IDJM8q+ZKDvokwY2icPxewAJZ2OtyGW55weDMTousWVEJoJ9oBiaXCb/ZOROJ8+Oyv8cR4Xbc8AZV3Ec4tusAcAFYoE7YCOwkSj7Beq7B3p16bfFcso8nA3GgGXx16qTCmEeCCS4alWFPE73AHlWknAaetWLlMMZIw6SURpkwSoALXe8DkvelkROc/uFlo2wypEswzLVW/dYpbHrU0U92OAQ==', - ] - const expectedEval = expectedEvals[_config.keystore.keys.domains.latest - 1] - - it('Should respond with 200 on valid request', async () => { - const [req, thresholdPoprfClient] = await signatureRequest() - - const res = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - signature: res.body.signature, - status: { - disabled: false, - counter: 1, - timer: res.body.status.timer, - now: res.body.status.now, - }, - }) - const evaluation = thresholdPoprfClient.unblindPartialResponse( - Buffer.from(res.body.signature, 'base64') - ) - expect(evaluation.toString('base64')).toEqual(expectedEval) - expect(res.get(KEY_VERSION_HEADER)).toEqual(_config.keystore.keys.domains.latest.toString()) - }) - - for (let i = 1; i <= 3; i++) { - it(`Should respond with 200 on valid request with key version header ${i}`, async () => { - const [req, thresholdPoprfClient] = await signatureRequest(undefined, undefined, i) - - const res = await request(app) - .post(SignerEndpoint.DOMAIN_SIGN) - .set(KEY_VERSION_HEADER, i.toString()) - .send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - signature: res.body.signature, - status: { - disabled: false, - counter: 1, - timer: res.body.status.timer, - now: res.body.status.now, - }, - }) - const evaluation = thresholdPoprfClient.unblindPartialResponse( - Buffer.from(res.body.signature, 'base64') - ) - expect(evaluation.toString('base64')).toEqual(expectedEvals[i - 1]) - expect(res.get(KEY_VERSION_HEADER)).toEqual(i.toString()) - }) - } - - it('Should respond with 200 on repeated valid requests with nonce updated', async () => { - const [req, thresholdPoprfClient] = await signatureRequest() - - const res1 = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(req) - - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual({ - success: true, - version: res1.body.version, - signature: res1.body.signature, - status: { - disabled: false, - counter: 1, - timer: res1.body.status.timer, - now: res1.body.status.now, - }, - }) - const eval1 = thresholdPoprfClient.unblindPartialResponse( - Buffer.from(res1.body.signature, 'base64') - ) - expect(eval1.toString('base64')).toEqual(expectedEval) - - // submit identical request with nonce set to 1 - req.options.nonce = defined(1) - // This is how - req.options.signature = noString - req.options.signature = defined( - await wallet.signTypedData(walletAddress, domainRestrictedSignatureRequestEIP712(req)) - ) - const res2 = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(req) - expect(res2.status).toBe(200) - expect(res2.body).toStrictEqual({ - success: true, - version: res2.body.version, - signature: res2.body.signature, - status: { - disabled: false, - counter: 2, - timer: res2.body.status.timer, - now: res2.body.status.now, - }, - }) - const eval2 = thresholdPoprfClient.unblindPartialResponse( - Buffer.from(res2.body.signature, 'base64') - ) - expect(eval2).toEqual(eval1) - }) - - it('Should respond with 200 if nonce > domainState', async () => { - const [req, thresholdPoprfClient] = await signatureRequest(undefined, 2) - const res = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(req) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - signature: res.body.signature, - status: { - disabled: false, - counter: 1, // counter gets incremented, not set to nonce value - timer: res.body.status.timer, - now: res.body.status.now, - }, - }) - const evaluation = thresholdPoprfClient.unblindPartialResponse( - Buffer.from(res.body.signature, 'base64') - ) - expect(evaluation.toString('base64')).toEqual(expectedEval) - }) - - it('Should respond with 200 on extra request fields', async () => { - const [req, thresholdPoprfClient] = await signatureRequest() - // @ts-ignore Intentionally adding an extra field to the request type - req.options.extraField = noString - - const res = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(req) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - signature: res.body.signature, - status: { - disabled: false, - counter: 1, - timer: res.body.status.timer, - now: res.body.status.now, - }, - }) - const evaluation = thresholdPoprfClient.unblindPartialResponse( - Buffer.from(res.body.signature, 'base64') - ) - expect(evaluation.toString('base64')).toEqual(expectedEval) - }) - - it('Should respond with 400 on missing request fields', async () => { - const [badRequest, _] = await signatureRequest() - // @ts-ignore Intentionally deleting required field - delete badRequest.domain.version - - const res = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(badRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on unknown domain', async () => { - // Create a request with an invalid domain identifier. - const [unknownRequest, _] = await signatureRequest() - // @ts-ignore UnknownDomain is (intentionally) not a valid domain identifier. - unknownRequest.domain.name = 'UnknownDomain' - - const res = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(unknownRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on bad encoding', async () => { - const [badRequest1, _] = await signatureRequest() - // @ts-ignore Intentionally not JSON - badRequest1.domain = 'Freddy' - - const res1 = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(badRequest1) - - expect(res1.status).toBe(400) - expect(res1.body).toStrictEqual({ - success: false, - version: res1.body.version, - error: WarningMessage.INVALID_INPUT, - }) - - const badRequest2 = '' - - const res2 = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(badRequest2) - - expect(res2.status).toBe(400) - expect(res2.body).toStrictEqual({ - success: false, - version: res2.body.version, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on invalid key version', async () => { - const [badRequest, _] = await signatureRequest() - - const res = await request(app) - .post(SignerEndpoint.DOMAIN_SIGN) - .set(KEY_VERSION_HEADER, 'a') - .send(badRequest) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.INVALID_KEY_VERSION_REQUEST, - }) - }) - - it('Should respond with 401 on failed auth', async () => { - // Create a manipulated request, which will have a bad signature. - const [badRequest, _] = await signatureRequest() - badRequest.domain.salt = defined('badSalt') - - const res = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(badRequest) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond 401 on invalid nonce', async () => { - // Request must be sent first since nonce check is >= 0 - const [req1, _] = await signatureRequest() - const res1 = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(req1) - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual({ - success: true, - version: res1.body.version, - signature: res1.body.signature, - status: { - disabled: false, - counter: 1, - timer: res1.body.status.timer, - now: res1.body.status.now, - }, - }) - const res2 = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(req1) - expect(res2.status).toBe(401) - - expect(res2.body).toStrictEqual({ - success: false, - version: res2.body.version, - error: WarningMessage.INVALID_NONCE, - status: { - disabled: false, - counter: 1, - timer: res1.body.status.timer, // Timer should be same as from first request - now: res2.body.status.now, - }, - }) - }) - - it('Should respond with 429 on out of quota', async () => { - const noQuotaDomain = authenticatedDomain([ - { delay: 0, resetTimer: noBool, batchSize: defined(0), repetitions: defined(0) }, - ]) - const [badRequest, _] = await signatureRequest(noQuotaDomain) - - const res = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(badRequest) - - expect(res.status).toBe(429) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.EXCEEDED_QUOTA, - status: { - disabled: false, - counter: 0, - timer: 0, - now: res.body.status.now, - }, - }) - }) - - it('Should respond with 429 on request too early', async () => { - // This domain won't accept requests until ~10 seconds after test execution - const noQuotaDomain = authenticatedDomain([ - { - delay: Math.floor(Date.now() / 1000) + 10, - resetTimer: noBool, - batchSize: defined(2), - repetitions: defined(1), - }, - ]) - const [badRequest, _] = await signatureRequest(noQuotaDomain) - - const res = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(badRequest) - - expect(res.status).toBe(429) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.EXCEEDED_QUOTA, - status: { - disabled: false, - counter: 0, - timer: 0, - now: res.body.status.now, - }, - }) - }) - - it('Should respond with 500 on unsupported key version', async () => { - const [req, _] = await signatureRequest(undefined, undefined, 4) - - const res = await request(app) - .post(SignerEndpoint.DOMAIN_SIGN) - .set(KEY_VERSION_HEADER, '4') - .send(req) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - // error: WarningMessage.INVALID_KEY_VERSION_REQUEST, - error: ErrorMessage.UNKNOWN_ERROR, // TODO make this more informative when we refactor the sign handler - }) - }) - - it('Should respond with 503 on disabled api', async () => { - const configWithApiDisabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithApiDisabled.api.domains.enabled = false - const appWithApiDisabled = startSigner(configWithApiDisabled, db, keyProvider) - - const [req, _] = await signatureRequest() - - const res = await request(appWithApiDisabled).post(SignerEndpoint.DOMAIN_SIGN).send(req) - - expect(res.status).toBe(503) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - - describe('functionality in case of errors', () => { - it('Should respond with 500 on DB getDomainStateRecord query failure', async () => { - const [req, _] = await signatureRequest() - // Mocking getDomainStateRecord directly but requiring the real version of - // getDomainStateRecordOrEmpty does not easily work, - // which is why we mock the outer call here & use the countAndThrowDBError - // helper to get as close as possible to testing a real error. - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/domain-state'), - 'getDomainStateRecordOrEmpty' - ) - .mockImplementationOnce(() => { - countAndThrowDBError( - new Error(), - rootLogger(_config.serviceName), - ErrorMessage.DATABASE_GET_FAILURE - ) - }) - const res = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(req) - spy.mockRestore() - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.DATABASE_GET_FAILURE, - }) - expect(await getDomainStateRecord(db, req.domain, rootLogger(_config.serviceName))).toBe( - null - ) - }) - - it('Should respond with 500 on DB updateDomainStateRecord failure', async () => { - const [req, _] = await signatureRequest() - // Same as above (re: getDomainStateRecord, but with insertDomainStateRecord) - // which is why we mock the outer call here & use the countAndThrowDBError - // helper to get as close as possible to testing a real error. - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/domain-state'), - 'updateDomainStateRecord' - ) - .mockImplementationOnce(() => { - countAndThrowDBError( - new Error(), - rootLogger(_config.serviceName), - ErrorMessage.DATABASE_UPDATE_FAILURE - ) - }) - const res = await request(app).post(SignerEndpoint.DOMAIN_SIGN).send(req) - spy.mockRestore() - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.DATABASE_UPDATE_FAILURE, - }) - expect(await getDomainStateRecord(db, req.domain, rootLogger(_config.serviceName))).toBe( - null - ) - }) - - it('Should respond with 500 on signer timeout', async () => { - const [req, _] = await signatureRequest() - const testTimeoutMS = 0 - const delay = 200 - - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/domain-state'), - 'getDomainStateRecordOrEmpty' - ) - .mockImplementationOnce(async () => { - await new Promise((resolve) => setTimeout(resolve, testTimeoutMS + delay)) - return createEmptyDomainStateRecord(req.domain) - }) - - const configWithShortTimeout = JSON.parse(JSON.stringify(_config)) - configWithShortTimeout.timeout = testTimeoutMS - const appWithShortTimeout = startSigner(configWithShortTimeout, db, keyProvider) - - const res = await request(appWithShortTimeout).post(SignerEndpoint.DOMAIN_SIGN).send(req) - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - error: ErrorMessage.TIMEOUT_FROM_SIGNER, - version: expectedVersion, - }) - spy.mockRestore() - }) - }) - }) -}) diff --git a/packages/phone-number-privacy/signer/test/integration/pnp.test.ts b/packages/phone-number-privacy/signer/test/integration/pnp.test.ts deleted file mode 100644 index 87b7b15d47..0000000000 --- a/packages/phone-number-privacy/signer/test/integration/pnp.test.ts +++ /dev/null @@ -1,1122 +0,0 @@ -import { newKit } from '@celo/contractkit' -import { - AuthenticationMethod, - ErrorMessage, - KEY_VERSION_HEADER, - PhoneNumberPrivacyRequest, - PnpQuotaResponseFailure, - PnpQuotaResponseSuccess, - rootLogger, - SignerEndpoint, - SignMessageResponseFailure, - SignMessageResponseSuccess, - TestUtils, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { BLINDED_PHONE_NUMBER } from '@celo/phone-number-privacy-common/lib/test/values' -import BigNumber from 'bignumber.js' -import { Knex } from 'knex' -import request from 'supertest' -import { initDatabase } from '../../src/common/database/database' -import { countAndThrowDBError } from '../../src/common/database/utils' -import { - getPerformedQueryCount, - incrementQueryCount, -} from '../../src/common/database/wrappers/account' -import { getRequestIfExists } from '../../src/common/database/wrappers/request' -import { initKeyProvider } from '../../src/common/key-management/key-provider' -import { KeyProvider } from '../../src/common/key-management/key-provider-base' -import { config, getSignerVersion, SupportedDatabase, SupportedKeystore } from '../../src/config' -import { startSigner } from '../../src/server' - -const { - ContractRetrieval, - createMockContractKit, - createMockAccounts, - createMockOdisPayments, - getPnpQuotaRequest, - getPnpRequestAuthorization, - getPnpSignRequest, -} = TestUtils.Utils -const { PRIVATE_KEY1, ACCOUNT_ADDRESS1, mockAccount, DEK_PRIVATE_KEY, DEK_PUBLIC_KEY } = - TestUtils.Values - -jest.setTimeout(20000) - -const zeroBalance = new BigNumber(0) - -const mockOdisPaymentsTotalPaidCUSD = jest.fn() -const mockGetWalletAddress = jest.fn() -const mockGetDataEncryptionKey = jest.fn() - -const mockContractKit = createMockContractKit({ - [ContractRetrieval.getAccounts]: createMockAccounts( - mockGetWalletAddress, - mockGetDataEncryptionKey - ), - [ContractRetrieval.getOdisPayments]: createMockOdisPayments(mockOdisPaymentsTotalPaidCUSD), -}) -jest.mock('@celo/contractkit', () => ({ - ...jest.requireActual('@celo/contractkit'), - newKit: jest.fn().mockImplementation(() => mockContractKit), -})) - -// Indexes correspond to keyVersion - 1 -const expectedSignatures: string[] = [ - 'MAAAAAAAAACEVdw1ULDwAiTcZuPnZxHHh38PNa+/g997JgV10QnEq9yeuLxbM9l7vk0EAicV7IAAAAAA', - 'MAAAAAAAAAAmUJY0s9p7fMfs7GIoSiGJoObAN8ZpA7kRqeC9j/Q23TBrG3Jtxc8xWibhNVZhbYEAAAAA', - 'MAAAAAAAAAC4aBbzhHvt6l/b+8F7cILmWxZZ5Q7S6R4RZ/IgZR7Pfb9B1Wg9fsDybgxVTSv5BYEAAAAA', -] - -describe('pnp', () => { - let keyProvider: KeyProvider - let app: any - let db: Knex - - const onChainBalance = new BigNumber(1e18) - const expectedQuota = 1000 - const expectedVersion = getSignerVersion() - - // create deep copy - const _config: typeof config = JSON.parse(JSON.stringify(config)) - _config.db.type = SupportedDatabase.Sqlite - _config.keystore.type = SupportedKeystore.MOCK_SECRET_MANAGER - _config.api.phoneNumberPrivacy.enabled = true - - const expectedSignature = expectedSignatures[_config.keystore.keys.phoneNumberPrivacy.latest - 1] - - beforeAll(async () => { - keyProvider = await initKeyProvider(_config) - }) - - beforeEach(async () => { - // Create a new in-memory database for each test. - db = await initDatabase(_config) - app = startSigner(_config, db, keyProvider, newKit('dummyKit')) - mockOdisPaymentsTotalPaidCUSD.mockReset() - mockGetDataEncryptionKey.mockReset().mockReturnValue(DEK_PUBLIC_KEY) - mockGetWalletAddress.mockReset().mockReturnValue(mockAccount) - }) - - afterEach(async () => { - // Close and destroy the in-memory database. - // Note: If tests start to be too slow, this could be replaced with more complicated logic to - // reset the database state without destroying and recreting it for each test. - await db?.destroy() - }) - - describe(`${SignerEndpoint.STATUS}`, () => { - it('Should return 200 and correct version', async () => { - const res = await request(app).get(SignerEndpoint.STATUS) - expect(res.status).toBe(200) - expect(res.body.version).toBe(expectedVersion) - }) - }) - - const sendRequest = async ( - req: PhoneNumberPrivacyRequest, - authorization: string, - endpoint: SignerEndpoint, - keyVersionHeader?: string, - signerApp: any = app - ) => { - const _req = request(signerApp).post(endpoint).set('Authorization', authorization) - - if (keyVersionHeader !== undefined) { - _req.set(KEY_VERSION_HEADER, keyVersionHeader) - } - - return _req.send(req) - } - - type pnpQuotaTestCase = { - cusdOdisPaymentInWei: BigNumber - expectedTotalQuota: number - } - const quotaCalculationTestCases: pnpQuotaTestCase[] = [ - { - cusdOdisPaymentInWei: new BigNumber(0), - expectedTotalQuota: 0, - }, - { - cusdOdisPaymentInWei: new BigNumber(1), - expectedTotalQuota: 0, - }, - { - cusdOdisPaymentInWei: new BigNumber(1.56e18), - expectedTotalQuota: 1560, - }, - { - // Sanity check for the default values to be used in endpoint setup tests - cusdOdisPaymentInWei: onChainBalance, - expectedTotalQuota: expectedQuota, - }, - { - // Unrealistically large amount paid for ODIS quota - cusdOdisPaymentInWei: new BigNumber(1.23456789e26), - expectedTotalQuota: 123456789000, - }, - ] - - describe(`${SignerEndpoint.PNP_QUOTA}`, () => { - describe('quota calculation logic', () => { - quotaCalculationTestCases.forEach(({ cusdOdisPaymentInWei, expectedTotalQuota }) => { - it(`Should get totalQuota=${expectedTotalQuota} - for cUSD (wei) payment=${cusdOdisPaymentInWei.toString()}`, async () => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValue(cusdOdisPaymentInWei) - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS1) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_QUOTA) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - performedQueryCount: 0, - totalQuota: expectedTotalQuota, - warnings: [], - }) - }) - }) - }) - - describe('endpoint functionality', () => { - // Use values already tested in quota logic tests, [onChainBalance, expectedQuota] - beforeEach(async () => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValue(onChainBalance) - }) - - it('Should respond with 200 on valid request', async () => { - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS1) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_QUOTA) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - performedQueryCount: 0, - totalQuota: expectedQuota, - warnings: [], - }) - }) - - it('Should respond with 200 on repeated valid requests', async () => { - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS1) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - - const res1 = await sendRequest(req, authorization, SignerEndpoint.PNP_QUOTA) - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual({ - success: true, - version: res1.body.version, - performedQueryCount: 0, - totalQuota: expectedQuota, - warnings: [], - }) - const res2 = await sendRequest(req, authorization, SignerEndpoint.PNP_QUOTA) - expect(res2.status).toBe(200) - expect(res2.body).toStrictEqual(res1.body) - }) - - it('Should respond with 200 on valid request when authenticated with DEK', async () => { - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS1, AuthenticationMethod.ENCRYPTION_KEY) - const authorization = getPnpRequestAuthorization(req, DEK_PRIVATE_KEY) - - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_QUOTA) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - performedQueryCount: 0, - totalQuota: expectedQuota, - warnings: [], - }) - }) - - it('Should respond with 200 on extra request fields', async () => { - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS1) - // @ts-ignore Intentionally adding an extra field to the request type - req.extraField = 'dummyString' - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_QUOTA) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - performedQueryCount: 0, - totalQuota: expectedQuota, - warnings: [], - }) - }) - - it('Should respond with 200 if performedQueryCount is greater than totalQuota', async () => { - await db.transaction(async (trx) => { - for (let i = 0; i <= expectedQuota; i++) { - await incrementQueryCount(db, ACCOUNT_ADDRESS1, rootLogger(config.serviceName), trx) - } - }) - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS1) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_QUOTA) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - performedQueryCount: expectedQuota + 1, - totalQuota: expectedQuota, - warnings: [], - }) - }) - - it('Should respond with 400 on missing request fields', async () => { - const badRequest = getPnpQuotaRequest(ACCOUNT_ADDRESS1) - // @ts-ignore Intentionally deleting required field - delete badRequest.account - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.PNP_QUOTA) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed WALLET_KEY auth', async () => { - // Request from one account, signed by another account - const badRequest = getPnpQuotaRequest(mockAccount, AuthenticationMethod.WALLET_KEY) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.PNP_QUOTA) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: res.body.version, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 401 on failed DEK auth', async () => { - const badRequest = getPnpQuotaRequest(ACCOUNT_ADDRESS1, AuthenticationMethod.ENCRYPTION_KEY) - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(badRequest, differentPk) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.PNP_QUOTA) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 503 on disabled api', async () => { - const configWithApiDisabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithApiDisabled.api.phoneNumberPrivacy.enabled = false - const appWithApiDisabled = startSigner( - configWithApiDisabled, - db, - keyProvider, - newKit('dummyKit') - ) - - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS1) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.PNP_QUOTA, - undefined, - appWithApiDisabled - ) - expect(res.status).toBe(503) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - - describe('functionality in case of errors', () => { - it('Should respond with 500 on DB performedQueryCount query failure', async () => { - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/account'), - 'getPerformedQueryCount' - ) - .mockRejectedValueOnce(new Error()) - - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS1) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_QUOTA) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.FAILURE_TO_GET_PERFORMED_QUERY_COUNT, - }) - - spy.mockRestore() - }) - - it('Should respond with 500 on blockchain totalQuota query failure', async () => { - mockOdisPaymentsTotalPaidCUSD.mockImplementation(() => { - throw new Error('dummy error') - }) - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS1) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_QUOTA) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.FAILURE_TO_GET_TOTAL_QUOTA, - }) - }) - - it('Should respond with 500 on signer timeout', async () => { - const testTimeoutMS = 0 - const delay = 100 - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/account'), - 'getPerformedQueryCount' - ) - .mockImplementation(async () => { - await new Promise((resolve) => setTimeout(resolve, testTimeoutMS + delay)) - return expectedQuota - }) - - const configWithShortTimeout = JSON.parse(JSON.stringify(_config)) - configWithShortTimeout.timeout = testTimeoutMS - const appWithShortTimeout = startSigner( - configWithShortTimeout, - db, - keyProvider, - newKit('dummyKit') - ) - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS1) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.PNP_QUOTA, - undefined, - appWithShortTimeout - ) - // Ensure that this is restored before test can fail on assertions - // to prevent failures in other tests - spy.mockRestore() - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - error: ErrorMessage.TIMEOUT_FROM_SIGNER, - version: expectedVersion, - }) - // Allow time for non-killed processes to finish - await new Promise((resolve) => setTimeout(resolve, delay)) - }) - }) - }) - }) - - describe(`${SignerEndpoint.PNP_SIGN}`, () => { - describe('quota calculation logic', () => { - quotaCalculationTestCases.forEach(({ expectedTotalQuota, cusdOdisPaymentInWei }) => { - it(`Should get totalQuota=${expectedTotalQuota} - for cUSD (wei) payment=${cusdOdisPaymentInWei.toString()}`, async () => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValue(cusdOdisPaymentInWei) - - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - - const shouldSucceed = expectedTotalQuota > 0 - - if (shouldSucceed) { - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, // incremented for signature request - totalQuota: expectedTotalQuota, - warnings: [], - }) - } else { - expect(res.status).toBe(403) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: 0, - totalQuota: expectedTotalQuota, - error: WarningMessage.EXCEEDED_QUOTA, - }) - } - }) - }) - }) - - describe('endpoint functionality', () => { - // Use values already tested in quota logic tests, [onChainBalance, expectedQuota] - const performedQueryCount = 2 - - beforeEach(async () => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValue(onChainBalance) - await db.transaction(async (trx) => { - for (let i = 0; i < performedQueryCount; i++) { - await incrementQueryCount(db, ACCOUNT_ADDRESS1, rootLogger(_config.serviceName), trx) - } - }) - }) - - it('Should respond with 200 on valid request', async () => { - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: performedQueryCount + 1, - totalQuota: expectedQuota, - warnings: [], - }) - expect(res.get(KEY_VERSION_HEADER)).toEqual( - _config.keystore.keys.phoneNumberPrivacy.latest.toString() - ) - }) - - it('Should respond with 200 on valid request when authenticated with DEK', async () => { - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.ENCRYPTION_KEY - ) - const authorization = getPnpRequestAuthorization(req, DEK_PRIVATE_KEY) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: performedQueryCount + 1, - totalQuota: expectedQuota, - warnings: [], - }) - }) - - for (let i = 1; i <= 3; i++) { - it(`Should respond with 200 on valid request with key version header ${i}`, async () => { - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN, i.toString()) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignatures[i - 1], - performedQueryCount: performedQueryCount + 1, - totalQuota: expectedQuota, - warnings: [], - }) - expect(res.get(KEY_VERSION_HEADER)).toEqual(i.toString()) - }) - } - - it('Should respond with 200 and warning on repeated valid requests', async () => { - const logger = rootLogger(_config.serviceName) - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res1 = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: performedQueryCount + 1, - totalQuota: expectedQuota, - warnings: [], - }) - - const requestDbRecord = await getRequestIfExists( - db, - req.account, - req.blindedQueryPhoneNumber, - logger - ) - expect(requestDbRecord).toEqual({ - blinded_query: req.blindedQueryPhoneNumber, - caller_address: req.account, - signature: expectedSignature, - timestamp: requestDbRecord!.timestamp, - }) - - const res2 = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - expect(res2.status).toBe(200) - res1.body.warnings.push(WarningMessage.DUPLICATE_REQUEST_TO_GET_PARTIAL_SIG) - expect(res2.body).toStrictEqual(res1.body) - }) - - it('Should respond with 200 on extra request fields', async () => { - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - // @ts-ignore Intentionally adding an extra field to the request type - req.extraField = 'dummyString' - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: performedQueryCount + 1, - totalQuota: expectedQuota, - warnings: [], - }) - }) - - it('Should respond with 400 on missing request fields', async () => { - const badRequest = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - // @ts-ignore Intentionally deleting required field - delete badRequest.account - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.PNP_SIGN) - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on invalid key version', async () => { - const badRequest = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.PNP_SIGN, 'a') - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_KEY_VERSION_REQUEST, - }) - }) - - it('Should respond with 400 on invalid blinded message', async () => { - const badRequest = getPnpSignRequest( - ACCOUNT_ADDRESS1, - '+1234567890', - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.PNP_SIGN) - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on invalid address', async () => { - const badRequest = getPnpSignRequest( - '0xnotanaddress', - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.PNP_SIGN) - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed WALLET_KEY auth', async () => { - const badRequest = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(badRequest, differentPk) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.PNP_SIGN) - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 401 on failed DEK auth', async () => { - const badRequest = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.ENCRYPTION_KEY - ) - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(badRequest, differentPk) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.PNP_SIGN) - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 403 on out of quota', async () => { - // deplete user's quota - const remainingQuota = expectedQuota - performedQueryCount - await db.transaction(async (trx) => { - for (let i = 0; i < remainingQuota; i++) { - await incrementQueryCount(db, ACCOUNT_ADDRESS1, rootLogger(_config.serviceName), trx) - } - }) - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - expect(res.status).toBe(403) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: expectedQuota, - totalQuota: expectedQuota, - error: WarningMessage.EXCEEDED_QUOTA, - }) - }) - - it('Should respond with 403 if totalQuota and performedQueryCount are zero', async () => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValue(zeroBalance) - const spy = jest // for convenience so we don't have to refactor or reset the db just for this test - .spyOn( - jest.requireActual('../../src/common/database/wrappers/account'), - 'getPerformedQueryCount' - ) - .mockResolvedValueOnce(0) - - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - expect(res.status).toBe(403) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: 0, - totalQuota: 0, - error: WarningMessage.EXCEEDED_QUOTA, - }) - - spy.mockRestore() - }) - - it('Should respond with 403 if performedQueryCount is greater than totalQuota', async () => { - const expectedRemainingQuota = expectedQuota - performedQueryCount - await db.transaction(async (trx) => { - for (let i = 0; i <= expectedRemainingQuota; i++) { - await incrementQueryCount(db, ACCOUNT_ADDRESS1, rootLogger(_config.serviceName), trx) - } - }) - - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - expect(res.status).toBe(403) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: expectedQuota + 1, - totalQuota: expectedQuota, - error: WarningMessage.EXCEEDED_QUOTA, - }) - }) - - it('Should respond with 500 on unsupported key version', async () => { - const badRequest = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.PNP_SIGN, '4') - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: performedQueryCount, - totalQuota: expectedQuota, - error: ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, - }) - }) - - it('Should respond with 503 on disabled api', async () => { - const configWithApiDisabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithApiDisabled.api.phoneNumberPrivacy.enabled = false - const appWithApiDisabled = startSigner( - configWithApiDisabled, - db, - keyProvider, - newKit('dummyKit') - ) - - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.PNP_SIGN, - '1', - appWithApiDisabled - ) - expect(res.status).toBe(503) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - - describe('functionality in case of errors', () => { - it('Should return 500 on DB performedQueryCount query failure', async () => { - // deplete user's quota - const remainingQuota = expectedQuota - performedQueryCount - await db.transaction(async (trx) => { - for (let i = 0; i < remainingQuota; i++) { - await incrementQueryCount( - db, - - ACCOUNT_ADDRESS1, - rootLogger(_config.serviceName), - trx - ) - } - }) - // sanity check - expect( - await getPerformedQueryCount(db, ACCOUNT_ADDRESS1, rootLogger(_config.serviceName)) - ).toBe(expectedQuota) - - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/account'), - 'getPerformedQueryCount' - ) - .mockRejectedValueOnce(new Error()) - - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.FAILURE_TO_GET_PERFORMED_QUERY_COUNT, - }) - - spy.mockRestore() - }) - - it('Should respond with 500 on signer timeout', async () => { - const testTimeoutMS = 0 - const delay = 200 - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/account'), - 'getPerformedQueryCount' - ) - .mockImplementationOnce(async () => { - await new Promise((resolve) => setTimeout(resolve, testTimeoutMS + delay)) - return performedQueryCount - }) - - const configWithShortTimeout = JSON.parse(JSON.stringify(_config)) - configWithShortTimeout.timeout = testTimeoutMS - const appWithShortTimeout = startSigner( - configWithShortTimeout, - db, - keyProvider, - newKit('dummyKit') - ) - - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.PNP_SIGN, - undefined, - appWithShortTimeout - ) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - error: ErrorMessage.TIMEOUT_FROM_SIGNER, - version: expectedVersion, - }) - spy.mockRestore() - }) - - it('Should return 500 on blockchain totalQuota query failure', async () => { - mockOdisPaymentsTotalPaidCUSD.mockImplementation(() => { - throw new Error('dummy error') - }) - - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - - const configWithFailOpenDisabled: typeof _config = JSON.parse(JSON.stringify(_config)) - const appWithFailOpenDisabled = startSigner( - configWithFailOpenDisabled, - db, - keyProvider, - newKit('dummyKit') - ) - - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.PNP_SIGN, - '1', - appWithFailOpenDisabled - ) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.FAILURE_TO_GET_TOTAL_QUOTA, - }) - }) - - it('Should return 500 on failure to increment query count', async () => { - const logger = rootLogger(_config.serviceName) - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/account'), - 'incrementQueryCount' - ) - .mockImplementationOnce(() => { - countAndThrowDBError(new Error(), logger, ErrorMessage.DATABASE_UPDATE_FAILURE) - }) - - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - spy.mockRestore() - - expect.assertions(4) - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.DATABASE_UPDATE_FAILURE, - }) - - // check DB state: performedQueryCount was not incremented and request was not stored - expect(await getPerformedQueryCount(db, ACCOUNT_ADDRESS1, logger)).toBe( - performedQueryCount - ) - expect( - await getRequestIfExists(db, req.account, req.blindedQueryPhoneNumber, logger) - ).toBe(undefined) - }) - - it('Should return 500 on failure to store request', async () => { - const logger = rootLogger(_config.serviceName) - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/request'), - 'insertRequest' - ) - .mockImplementationOnce(() => { - countAndThrowDBError(new Error(), logger, ErrorMessage.DATABASE_INSERT_FAILURE) - }) - - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - spy.mockRestore() - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.DATABASE_INSERT_FAILURE, - }) - - // check DB state: performedQueryCount was not incremented and request was not stored - expect(await getPerformedQueryCount(db, ACCOUNT_ADDRESS1, logger)).toBe( - performedQueryCount - ) - expect( - await getRequestIfExists(db, req.account, req.blindedQueryPhoneNumber, logger) - ).toBe(undefined) - }) - - it('Should return 500 on bls signing error', async () => { - const spy = jest - .spyOn(jest.requireActual('blind-threshold-bls'), 'partialSignBlindedMessage') - .mockImplementationOnce(() => { - throw new Error() - }) - - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: performedQueryCount, - totalQuota: expectedQuota, - error: ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, - }) - - spy.mockRestore() - - // check DB state: performedQueryCount was not incremented and request was not stored - expect( - await getPerformedQueryCount( - db, - - ACCOUNT_ADDRESS1, - rootLogger(_config.serviceName) - ) - ).toBe(performedQueryCount) - expect( - await getRequestIfExists( - db, - req.account, - req.blindedQueryPhoneNumber, - rootLogger(_config.serviceName) - ) - ).toBe(undefined) - }) - - it('Should return 500 on generic error in sign', async () => { - const spy = jest - .spyOn( - jest.requireActual('../../src/common/bls/bls-cryptography-client'), - 'computeBlindedSignature' - ) - .mockImplementationOnce(() => { - // Trigger a generic error in .sign to trigger the default error returned. - throw new Error() - }) - - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: performedQueryCount, - totalQuota: expectedQuota, - error: ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, - }) - - spy.mockRestore() - - // check DB state: performedQueryCount was not incremented and request was not stored - expect( - await getPerformedQueryCount(db, ACCOUNT_ADDRESS1, rootLogger(config.serviceName)) - ).toBe(performedQueryCount) - expect( - await getRequestIfExists( - db, - - req.account, - req.blindedQueryPhoneNumber, - rootLogger(config.serviceName) - ) - ).toBe(undefined) - }) - }) - }) - }) -}) diff --git a/packages/phone-number-privacy/signer/test/key-management/aws-key-provider.test.ts b/packages/phone-number-privacy/signer/test/key-management/aws-key-provider.test.ts deleted file mode 100644 index ca870fb161..0000000000 --- a/packages/phone-number-privacy/signer/test/key-management/aws-key-provider.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { AWSKeyProvider } from '../../src/common/key-management/aws-key-provider' -import { DefaultKeyName, Key } from '../../src/common/key-management/key-provider-base' - -const mockKey = '010101010101010101010101010101010101010101010101010101010101010101010101' -const mockResponse = { SecretString: `{"mockSecretKey":"${mockKey}"}` } -const mockEmptyMockSecretKeyResponse = { SecretString: `{"mockSecretKey":""}` } -const mockBinaryResponse = { SecretBinary: Buffer.from(mockKey).toString('base64') } -const mockInvalidResponse1 = { foo: 'bar' } -const mockInvalidResponse2 = { SecretString: 'totally not a json string' } - -const key: Key = { - name: DefaultKeyName.PHONE_NUMBER_PRIVACY, - version: 1, -} - -jest.mock('../../src/config', () => ({ - config: { - serviceName: 'odis-signer', - keystore: { - keys: { - phoneNumberPrivacy: { - name: 'phoneNumberPrivacy', - latest: 1, - }, - domains: { - name: 'domains', - latest: 1, - }, - }, - aws: { - region: 'mockRegion', - secretKey: 'mockSecretKey', - }, - }, - }, -})) - -const getSecretValue = jest.fn() - -jest.mock('aws-sdk', () => ({ - SecretsManager: jest.fn(() => ({ - config: { - update: jest.fn(), - }, - getSecretValue, - })), -})) - -describe('AWSKeyProvider', () => { - describe('with valid input', () => { - it('parses string keys correctly', async () => { - getSecretValue.mockReturnValue({ promise: jest.fn().mockResolvedValue(mockResponse) }) - - const provider = new AWSKeyProvider() - await provider.fetchPrivateKeyFromStore(key) - expect(provider.getPrivateKey(key)).toBe(mockKey) - }) - - it('parses binary keys correctly', async () => { - getSecretValue.mockReturnValue({ promise: jest.fn().mockResolvedValue(mockBinaryResponse) }) - - const provider = new AWSKeyProvider() - await provider.fetchPrivateKeyFromStore(key) - expect(provider.getPrivateKey(key)).toBe(mockKey) - }) - }) - - describe('with invalid input', () => { - it('invalid keys are properly handled', async () => { - getSecretValue.mockReturnValue({ - promise: jest - .fn() - .mockResolvedValueOnce(mockInvalidResponse1) - .mockResolvedValueOnce(mockInvalidResponse2), - }) - - const provider = new AWSKeyProvider() - expect.assertions(2) - await expect(provider.fetchPrivateKeyFromStore(key)).rejects.toThrow() - await expect(provider.fetchPrivateKeyFromStore(key)).rejects.toThrow() - }) - - it('empty key is handled correctly', async () => { - getSecretValue.mockReturnValue({ - promise: jest.fn().mockResolvedValue(mockEmptyMockSecretKeyResponse), - }) - - const provider = new AWSKeyProvider() - expect.assertions(1) - await expect(provider.fetchPrivateKeyFromStore(key)).rejects.toThrow() - }) - }) -}) diff --git a/packages/phone-number-privacy/signer/test/key-management/azure-key-provider.test.ts b/packages/phone-number-privacy/signer/test/key-management/azure-key-provider.test.ts deleted file mode 100644 index 393abcdc38..0000000000 --- a/packages/phone-number-privacy/signer/test/key-management/azure-key-provider.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { AzureKeyProvider } from '../../src/common/key-management/azure-key-provider' -import { DefaultKeyName, Key } from '../../src/common/key-management/key-provider-base' - -const mockKey = '030303030303030303030303030303030303030303030303030303030303030303030303' - -const key: Key = { - name: DefaultKeyName.PHONE_NUMBER_PRIVACY, - version: 1, -} - -jest.mock('../../src/config', () => ({ - config: { - serviceName: 'odis-signer', - keystore: { - keys: { - phoneNumberPrivacy: { - name: 'phoneNumberPrivacy', - latest: 1, - }, - domains: { - name: 'domains', - latest: 1, - }, - }, - azure: { - clientID: 'mockClientID', - clientSecret: 'mockClientSecret', - tenant: 'mockTenant', - vaultName: 'mockVaultName', - }, - }, - }, -})) - -const getSecret = jest.fn() - -jest.mock('@celo/wallet-hsm-azure', () => ({ - AzureKeyVaultClient: jest.fn(() => ({ getSecret })), -})) - -describe('AzureKeyProvider', () => { - it('parses keys correctly', async () => { - getSecret.mockResolvedValue(mockKey) - - const provider = new AzureKeyProvider() - await provider.fetchPrivateKeyFromStore(key) - expect(provider.getPrivateKey(key)).toBe(mockKey) - }) - - it('handles exceptions correctly', async () => { - getSecret.mockRejectedValue(new Error('Secret retrieval exception')) - - const provider = new AzureKeyProvider() - expect.assertions(1) - await expect(provider.fetchPrivateKeyFromStore(key)).rejects.toThrow() - }) -}) diff --git a/packages/phone-number-privacy/signer/test/key-management/google-key-provider.test.ts b/packages/phone-number-privacy/signer/test/key-management/google-key-provider.test.ts deleted file mode 100644 index 284d6ffb43..0000000000 --- a/packages/phone-number-privacy/signer/test/key-management/google-key-provider.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { GoogleKeyProvider } from '../../src/common/key-management/google-key-provider' -import { DefaultKeyName, Key } from '../../src/common/key-management/key-provider-base' - -const mockKey = '020202020202020202020202020202020202020202020202020202020202020202020202' -const mockResponse = [{ payload: { data: `${mockKey}` } }] -const emptyMockResponse = [{ payload: {} }] -const invalidMockResponse = [{ payload: { data: '123' } }] -const key: Key = { - name: DefaultKeyName.PHONE_NUMBER_PRIVACY, - version: 1, -} - -jest.mock('../../src/config', () => ({ - config: { - serviceName: 'odis-signer', - keystore: { - keys: { - phoneNumberPrivacy: { - name: 'phoneNumberPrivacy', - latest: 1, - }, - domains: { - name: 'domains', - latest: 1, - }, - }, - google: { - projectId: 'mockProject', - }, - }, - }, -})) - -const accessSecretVersion = jest.fn() - -jest.mock('@google-cloud/secret-manager/build/src/v1', () => ({ - SecretManagerServiceClient: jest.fn(() => ({ accessSecretVersion })), -})) - -describe('GoogleKeyProvider', () => { - it('parses keys correctly', async () => { - accessSecretVersion.mockResolvedValue(mockResponse) - - const provider = new GoogleKeyProvider() - await provider.fetchPrivateKeyFromStore(key) - expect(provider.getPrivateKey(key)).toBe(mockKey) - }) - - it('handles errors correctly', async () => { - accessSecretVersion.mockResolvedValue(emptyMockResponse) - - const provider = new GoogleKeyProvider() - expect.assertions(1) - await expect(provider.fetchPrivateKeyFromStore(key)).rejects.toThrow() - }) - - it('unitialized provider throws', () => { - accessSecretVersion.mockResolvedValue(mockResponse) - - const provider = new GoogleKeyProvider() - expect.assertions(1) - expect(() => provider.getPrivateKey(key)).toThrow() - }) - - it('set invalid private key throws', async () => { - accessSecretVersion.mockResolvedValue(invalidMockResponse) - - const provider = new GoogleKeyProvider() - expect.assertions(1) - await expect(provider.fetchPrivateKeyFromStore(key)).rejects.toThrow() - }) -}) diff --git a/packages/phone-number-privacy/signer/test/pnp/services/request-service.test.ts b/packages/phone-number-privacy/signer/test/pnp/services/request-service.test.ts deleted file mode 100644 index f793e08910..0000000000 --- a/packages/phone-number-privacy/signer/test/pnp/services/request-service.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Knex } from 'knex' -import { initDatabase } from '../../../src/common/database/database' -import { config, SupportedDatabase, SupportedKeystore } from '../../../src/config' -import { - DefaultPnpRequestService, - PnpRequestService, -} from '../../../src/pnp/services/request-service' -import { rootLogger } from '@celo/phone-number-privacy-common' -import { - PnpSignRequestRecord, - REQUESTS_COLUMNS, - REQUESTS_TABLE, -} from '../../../src/common/database/models/request' - -jest.setTimeout(20000) -describe('request service', () => { - let db: Knex - let service: PnpRequestService - let ctx = { - logger: rootLogger('test'), - url: '', - errors: [], - } - - // create deep copy - const _config: typeof config = JSON.parse(JSON.stringify(config)) - _config.db.type = SupportedDatabase.Postgres - _config.keystore.type = SupportedKeystore.MOCK_SECRET_MANAGER - - beforeEach(async () => { - // Create a new in-memory database for each test. - db = await initDatabase(_config) - service = new DefaultPnpRequestService(db) - }) - - // Skipped because it fails in sqlite, works in the other database. - // Keep the test for future checks - it.skip('should remove requests from a specific date', async () => { - const fourDaysAgo = new Date(Date.now() - 4 * 24 * 60 * 60 * 1000) - await service.recordRequest('Address1', 'Blinded1', 'signature1', ctx) - await db(REQUESTS_TABLE).update({ - timestamp: fourDaysAgo, - }) - await service.recordRequest('Address2', 'Blinded2', 'signature2', ctx) - - const elements = await db(REQUESTS_TABLE) - .count(`${REQUESTS_COLUMNS.address} as CNT`) - .first() - - expect((elements! as any)['CNT']).toBe('2') - - await service.removeOldRequests(2, ctx) - - const elementsAfter = await db(REQUESTS_TABLE) - .count(`${REQUESTS_COLUMNS.address} as CNT`) - .first() - expect((elementsAfter! as any)['CNT']).toBe('1') - }) -}) diff --git a/packages/phone-number-privacy/signer/test/signing/bls-signature.test.ts b/packages/phone-number-privacy/signer/test/signing/bls-signature.test.ts deleted file mode 100644 index 779fbd6357..0000000000 --- a/packages/phone-number-privacy/signer/test/signing/bls-signature.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import threshold_bls from 'blind-threshold-bls' -import { rootLogger, TestUtils } from '@celo/phone-number-privacy-common' -import { computeBlindedSignature } from '../../src/common/bls/bls-cryptography-client' -import { config } from '../../src/config' - -describe(`BLS service computes signature`, () => { - it('provides blinded signature', async () => { - const message = Buffer.from('hello world') - const userSeed = new Uint8Array(32) - for (let i = 0; i < userSeed.length - 1; i++) { - userSeed[i] = i - } - - const blindedMsgResult = threshold_bls.blind(message, userSeed) - const blindedMsg = Buffer.from(blindedMsgResult.message).toString('base64') - - const actual = computeBlindedSignature( - blindedMsg, - TestUtils.Values.PNP_DEV_SIGNER_PRIVATE_KEY, - rootLogger(config.serviceName) - ) - expect(actual).toEqual( - 'MAAAAAAAAADDilSaA/xvbtE4NV3agMzHIf8PGPQ83Cu8gQy5E2mRWyUIges8bjE4EBe1L7pcY4AAAAAA' - ) - - expect( - threshold_bls.partialVerifyBlindSignature( - Buffer.from(TestUtils.Values.PNP_DEV_ODIS_POLYNOMIAL, 'hex'), - blindedMsgResult.message, - Buffer.from(actual, 'base64') - ) - ) - - const combinedSignature = threshold_bls.combine(1, Buffer.from(actual, 'base64')) - const unblindedSignedMessage = threshold_bls.unblind( - combinedSignature, - blindedMsgResult.blindingFactor - ) - const publicKey = Buffer.from(TestUtils.Values.PNP_DEV_ODIS_PUBLIC_KEY, 'base64') - expect(threshold_bls.verify(publicKey, message, unblindedSignedMessage)) - }) - - it('invalid blind message throws an error', async () => { - const blindedMsg = Buffer.from('invalid blinded message').toString('base64') - - expect.assertions(1) - expect(() => - computeBlindedSignature( - blindedMsg, - TestUtils.Values.PNP_DEV_SIGNER_PRIVATE_KEY, - rootLogger(config.serviceName) - ) - ).toThrow() - }) -}) diff --git a/packages/phone-number-privacy/signer/tsconfig.json b/packages/phone-number-privacy/signer/tsconfig.json deleted file mode 100644 index 73b06e83a5..0000000000 --- a/packages/phone-number-privacy/signer/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "plugins": [ - { - "name": "typescript-tslint-plugin" - } - ], - "lib": ["es2017", "ES2020.Promise"], - "module": "commonjs", - "strict": true, - "allowJs": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": true, - "target": "es2017", - "rootDir": "src", - "outDir": "./dist", - "skipLibCheck": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "preserveConstEnums": true, - "composite": true - }, - "include": ["src", "index.d.ts"], - "compileOnSave": true -} diff --git a/packages/phone-number-privacy/signer/tslint.json b/packages/phone-number-privacy/signer/tslint.json deleted file mode 100644 index 0902a7148c..0000000000 --- a/packages/phone-number-privacy/signer/tslint.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": ["@celo/typescript/tslint.json"], - "rules": { - "no-implicit-dependencies": [true, ["node:http"]], - "no-global-arrow-functions": false, - "no-console": true - } -} diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 51f1c82f97..990b9dce79 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -65,7 +65,9 @@ "@truffle/resolver": "9.0.27", "bignumber.js": "9.1.0", "bip39": "https://github.com/bitcoinjs/bip39#d8ea080a18b40f301d4e2219a2991cd2417e83c2", + "bn.js": "^5.1.0", "chai": "^4.3.6", + "chalk": "^2.4.2", "chai-subset": "^1.6.0", "csv-parser": "^2.0.0", "csv-stringify": "^4.3.1", @@ -80,6 +82,8 @@ "j6": "^1.0.2", "lodash": "^4.17.21", "mathjs": "^5.0.4", + "form-data": "^3.0.0", + "minimist": "^1.2.0", "node-fetch": "^2.6.9", "openzeppelin-solidity": "^2.5.0", "prompts": "^2.0.1", @@ -104,6 +108,7 @@ "@types/mathjs": "^4.4.1", "@types/mocha": "^7.0.2", "@types/targz": "^1.0.0", + "@types/lodash": "^4.14.199", "@types/tmp": "^0.1.0", "@types/yargs": "^13.0.2", "cross-env": "^5.1.6", @@ -114,6 +119,7 @@ "truffle-typings": "^1.0.6", "ts-node": "8.3.0", "typechain": "1.0.5", + "ts-generator": "^0.0.8", "typechain-target-truffle": "1.0.2", "yargs": "^14.0.0" } diff --git a/packages/protocol/tslint.json b/packages/protocol/tslint.json index 7fbfa85004..59a3611ad0 100644 --- a/packages/protocol/tslint.json +++ b/packages/protocol/tslint.json @@ -5,7 +5,15 @@ "exclude": ["types/contracts/*", "lib/**"] }, "rules": { - "no-implicit-dependencies": false, + "no-implicit-dependencies": [ + true, + "dev", + [ + "contractPackages", + "@celo/protocol", + "types" + ] + ], "no-global-arrow-functions": false, "no-floating-promises": true, "no-string-literal": false, diff --git a/packages/sdk/encrypted-backup/.gitignore b/packages/sdk/encrypted-backup/.gitignore deleted file mode 100644 index 7fabe89f61..0000000000 --- a/packages/sdk/encrypted-backup/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -lib/ -tmp/ -.tmp/ -.env \ No newline at end of file diff --git a/packages/sdk/encrypted-backup/.npmignore b/packages/sdk/encrypted-backup/.npmignore deleted file mode 100644 index 45e506bacd..0000000000 --- a/packages/sdk/encrypted-backup/.npmignore +++ /dev/null @@ -1,17 +0,0 @@ -/.devchain/ -/.devchain.tar.gz -/coverage/ -/node_modules/ -/src/ -/tmp/ -/.tmp/ - -/tslint.json -/tsconfig.* -/jest.config.* -*.tgz - -/src - -/lib/**/*.test.* -/lib/test-utils \ No newline at end of file diff --git a/packages/sdk/encrypted-backup/README.md b/packages/sdk/encrypted-backup/README.md deleted file mode 100644 index 1396599940..0000000000 --- a/packages/sdk/encrypted-backup/README.md +++ /dev/null @@ -1,125 +0,0 @@ -# Pin/Password-Encrypted Backup SDK - -## Overview - -This package provides an SDK for creating PIN/password encrypted user data backups, as is used in -the [PIN/Password Encrypted Account Recovery (PEAR)]. - -[PIN/Password Encrypted Account Recovery (PEAR)]: https://docs.celo.org/celo-codebase/protocol/identity/encrypted-cloud-backup - -It includes functionality to create, serialize, deserialize, and open encrypted backups. - -**Note that this SDK relies on the [Domains extension] to ODIS, which is not currently deployed to -the Mainnet operators** - -[Domains extension]: https://docs.celo.org/celo-codebase/protocol/odis/domains - -Developers can integrate with two preconfigured encryption profiles with the functions, -`createPinEncryptedBackup` and `createPasswordEncryptedBackup` which apply recommended levels of -hardening to generate the backup encryption key from the given PIN or password respectively. - -### Configuration - -Developers who want more control over the security parameters and user experience trade-offs (e.g. -more restrictive rate limiting for higher security at the cost of higher friction), may define their -own hardening configurations and use the `createBackup` function to build those backups. - -If the available ODIS hardening profiles do not work for your use case, the ODIS [Domains extension] -which underlies the key hardening is designed to be readily extensible. Feel free to propose a new -rate limiting function by filing an issue or opening a PR. You can read [CIP-40] for more -information about what is possible. - -[Domains extension]: https://docs.celo.org/celo-codebase/protocol/odis/domains -[CIP-40]: https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0040.md - -### Key hardening - -PIN or password derived encryption keys are not secure by default because these secrets do not have -enough entropy (i.e. they are too easy to guess). As a result key hardening is required to ensure -the derived key is strong enough to withstand a motivated attackers. - -### ODIS - -The primary method of key hardening used in this SDK is the ODIS [POPRF] functionality, exposed -through the [Domains extension] for [key hardening]. This uses ODIS as a "hashing service" with an -applied rate limit that is configurable by the client. The enforcement of the rate limit makes -guessing a password or PIN infeasible by greatly restricting the amount of attempts an attacker gets -to guess the key. This necessarily also limits the number of attempts the user gets. - -[POPRF]: https://github.com/celo-org/celo-poprf-rs -[Domains extension]: https://docs.celo.org/celo-codebase/protocol/odis/domains -[key hardening]: https://docs.celo.org/celo-codebase/protocol/odis/use-cases/key-hardening - -### Additional hardening - -In addition to ODIS, this library supports two more sources of key hardening. - -First, it supports computational key hardening through `scrypt` or `PBKDF2`. These functions impose -a computational cost on checking a password guess. For secrets such as passwords with a moderate -amount of entropy, this can be a useful line of defense in preventing key cracking. The default -password hardening configuration includes `scrypt` hardening. In the case of low entropy secrets -such as PINs, it does not provide a meaningful increase in security. - -Second, it supports the use of a "circuit breaker" service. When enabled, the key generation process -will include a random "fuse key". Once mixed into the encryption key, the fuse key is encrypted to -the public key of the circuit breaker service and then discarded. The fuse key ciphertext is -included in the backup. Under normal circumstances, the user can send the fuse key ciphertext to the -circuit breaker service to have it decrypted. If ODIS is ever believed to be compromised, the -circuit breaker operator will disable this service, disabling recovery of the fuse key and -decryption of the backup data. Using this service represents a trade-off of electing a third-party -with the right to disable backup access for the benefit of security against attackers that might -successfully compromise ODIS. The PIN default hardening configuration includes this service, -electing [Valora] as the circuit breaker operator. - -[Valora]: https://valoraapp.com/ - -## Documentation - -The best resource for understanding the library is the inline documentation in [backup.ts] or the -SDK documentation at [celo-sdk-docs.readthedocs.io]. - -[backup.ts]: src/backup.ts -[celo-sdk-docs.readthedocs.io]: https://celo-sdk-docs.readthedocs.io/en/latest/encrypted-backup/ - -## Examples - -### Creating a backup - -In order to create a backup, the application should serialize the account data (e.g. seed phrase, user -name, wallet metadata, etc) into a `Buffer` and obtain a PIN or password from the user. In this -example, we use a PIN. - -Calling `createPinEncryptedBackup` will use ODIS for key hardening, add a circuit breaker key, and -bundle the result into a `Backup` object. The application should store this data somewhere the user -can access it for recovery. Although it is encrypted, it should not be exposed publicly and -instead should be stored in consumer cloud storage like Google Drive or iCloud, or on the -application servers with some application specific authentication. - -```typescript -import { createPinEncryptedBackup } from '@celo/encrypted-backup' - -const backup = await createPinEncryptedBackup(accountData, userPin) -if (!backup.ok) { - // handle backup.error -} -storeUserBackup(backup.result) -``` - -### Opening a backup - -In order to open the backup, the application should fetch the user backup and ask the user to enter -their PIN or password. Calling `openBackup` will read the backup to determine what hardening was -applied, the make the required calls ODIS, a circuit breaker service, and computational hardening to -recover the encryption key and decrypt the backup. The backup data can then be used to restore the -user account. - -```typescript -import { openBackup } from '@celo/encrypted-backup' - -const backup = await fetchUserBackup() -const accountData = await openBackup(backup, userPin) -if (!accountKey.ok) { - // handle backup.error -} -restoreAccount(accountData.result) -``` diff --git a/packages/sdk/encrypted-backup/jest.config.js b/packages/sdk/encrypted-backup/jest.config.js deleted file mode 100644 index 31e6b5e748..0000000000 --- a/packages/sdk/encrypted-backup/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testMatch: ['/src/**/?(*.)+(spec|test).ts?(x)'], - setupFilesAfterEnv: ['@celo/dev-utils/lib/matchers', '/jestSetup.ts'], - verbose: true, -} diff --git a/packages/sdk/encrypted-backup/jestSetup.ts b/packages/sdk/encrypted-backup/jestSetup.ts deleted file mode 100644 index 0e7775597a..0000000000 --- a/packages/sdk/encrypted-backup/jestSetup.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Setup mock for the fetch API to intercept requests to ODIS and the circuit breaker service. -// cross-fetch is used by the @celo/identity library. -const fetchMockSandbox = require('fetch-mock').sandbox() -jest.mock('cross-fetch', () => fetchMockSandbox) - -// @ts-ignore -global.fetchMock = fetchMockSandbox diff --git a/packages/sdk/encrypted-backup/package.json b/packages/sdk/encrypted-backup/package.json deleted file mode 100644 index 854c42cf10..0000000000 --- a/packages/sdk/encrypted-backup/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@celo/encrypted-backup", - "version": "5.0.4", - "description": "Libraries for implemented password encrypted account backups", - "main": "./lib/index.js", - "types": "./lib/index.d.ts", - "author": "Celo", - "license": "Apache-2.0", - "homepage": "https://celo-sdk-docs.readthedocs.io/en/latest/encrypted-backup", - "repository": "https://github.com/celo-org/celo-monorepo/tree/master/packages/sdk/encrypted-backup", - "keywords": [ - "celo", - "blockchain", - "odis", - "backup", - "encrypted", - "encrypted-backup" - ], - "scripts": { - "build": "tsc -b .", - "clean": "tsc -b . --clean", - "docs": "typedoc", - "test": "jest --runInBand", - "lint": "tslint -c tslint.json --project .", - "prepublishOnly": "yarn build" - }, - "dependencies": { - "@celo/connect": "5.0.4", - "@celo/base": "5.0.4", - "@celo/identity": "5.0.4", - "@celo/wallet-local": "5.0.4", - "@celo/phone-number-privacy-common": "^3.0.3", - "@celo/poprf": "^0.1.9", - "@celo/utils": "5.0.4", - "@types/debug": "^4.1.5", - "debug": "^4.1.1", - "fp-ts": "2.1.1", - "io-ts": "2.0.1" - }, - "devDependencies": { - "@celo/dev-utils": "0.0.1", - "fetch-mock": "9.10.4" - }, - "engines": { - "node": ">=8.13.0" - } -} \ No newline at end of file diff --git a/packages/sdk/encrypted-backup/src/backup.test.ts b/packages/sdk/encrypted-backup/src/backup.test.ts deleted file mode 100644 index 9e6cb75e79..0000000000 --- a/packages/sdk/encrypted-backup/src/backup.test.ts +++ /dev/null @@ -1,421 +0,0 @@ -import { - CircuitBreakerErrorTypes, - CircuitBreakerKeyStatus, -} from '@celo/identity/lib/odis/circuit-breaker' -import { MockCircuitBreaker } from '@celo/identity/lib/odis/circuit-breaker.mock' -import { defined, noBool } from '@celo/utils/lib/sign-typed-data-utils' -import debugFactory from 'debug' -import { Backup, createBackup, openBackup } from './backup' -import { ComputationalHardeningFunction, HardeningConfig } from './config' -import { BackupErrorTypes } from './errors' -import { MockOdis } from './odis.mock' -import { deserializeBackup, serializeBackup } from './schema' - -const debug = debugFactory('kit:encrypted-backup:backup:test') - -const TEST_HARDENING_CONFIG: HardeningConfig = { - odis: { - rateLimit: [{ delay: 0, resetTimer: noBool, batchSize: defined(3), repetitions: defined(1) }], - environment: MockOdis.environment, - }, - circuitBreaker: { - environment: MockCircuitBreaker.environment, - }, - computational: { - function: ComputationalHardeningFunction.SCRYPT, - cost: 1024, - }, -} - -let mockOdis: MockOdis | undefined -let mockCircuitBreaker: MockCircuitBreaker | undefined - -beforeEach(() => { - fetchMock.reset() - fetchMock.config.overwriteRoutes = true - - // Mock ODIS using the mock implementation defined above. - mockOdis = new MockOdis() - mockOdis.install(fetchMock) - - // Mock the circuit breaker service using the implementation from the identity library. - mockCircuitBreaker = new MockCircuitBreaker() - mockCircuitBreaker.install(fetchMock) -}) - -afterEach(() => { - fetchMock.reset() -}) - -describe('end-to-end', () => { - it('should be able to create, serialize, deserialize, and open a backup', async () => { - const testData = Buffer.from('backup test data', 'utf8') - const testPassword = Buffer.from('backup test password', 'utf8') - const testMetadata = { - name: 'test backup', - timestamp: Date.now(), - } - - const backup = await createBackup({ - data: testData, - userSecret: testPassword, - hardening: TEST_HARDENING_CONFIG, - metadata: testMetadata, - }) - debug('Created backup result', backup) - expect(backup.ok).toBe(true) - if (!backup.ok) { - return - } - expect(backup.result.metadata).toEqual(testMetadata) - - // Attempt to open the backup before passing it through the serialize function. - const opened = await openBackup({ backup: backup.result, userSecret: testPassword }) - debug('Open backup result', opened) - expect(opened.ok).toBe(true) - if (!opened.ok) { - return - } - expect(opened.result).toEqual(testData) - - // Serialize the backup. - const serialized = serializeBackup(backup.result) - debug('Serialized backup', serialized) - - // Deserialize the backup, check that it is correctly deserialized and can be opened. - const deserialized = deserializeBackup(serialized) - debug('Deserialize backup result', deserialized) - expect(deserialized.ok).toBe(true) - if (!deserialized.ok) { - return - } - expect(deserialized.result).toEqual(backup.result) - expect(deserialized.result.metadata).toEqual(testMetadata) - - // Open the backup and check that that the expect data is recovered. - const reopened = await openBackup({ backup: deserialized.result, userSecret: testPassword }) - debug('Reopen backup result', reopened) - expect(reopened.ok).toBe(true) - if (!reopened.ok) { - return - } - expect(reopened.result).toEqual(testData) - }) -}) - -describe('createBackup', () => { - const testData = Buffer.from('backup test data', 'utf8') - const testPassword = Buffer.from('backup test password', 'utf8') - - it('should return a fetch error when request to ODIS fails', async () => { - mockOdis!.installSignEndpoint(fetchMock, { throws: new Error('fetch failed') }) - const result = await createBackup({ - data: testData, - userSecret: testPassword, - hardening: TEST_HARDENING_CONFIG, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(BackupErrorTypes.FETCH_ERROR) - }) - - it('should return a service error when ODIS returns an error status', async () => { - mockOdis!.installSignEndpoint(fetchMock, { status: 501 }) - const result = await createBackup({ - data: testData, - userSecret: testPassword, - hardening: TEST_HARDENING_CONFIG, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(BackupErrorTypes.ODIS_SERVICE_ERROR) - }) - - it('should return a rate limit error when ODIS returns a rate limiting status', async () => { - mockOdis!.installSignEndpoint(fetchMock, { status: 429, headers: { 'Retry-After': '60' } }) - const result = await createBackup({ - data: testData, - userSecret: testPassword, - hardening: TEST_HARDENING_CONFIG, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(BackupErrorTypes.ODIS_RATE_LIMITING_ERROR) - }) - - it('should return a rate limit error when ODIS indicates no quota available', async () => { - mockOdis!.installQuotaEndpoint(fetchMock, { - status: 200, - body: { - success: true, - version: 'mock', - status: { - timer: Date.now() / 1000 + 3600, - counter: 0, - disabled: false, - now: Date.now() / 1000, - }, - }, - }) - const result = await createBackup({ - data: testData, - userSecret: testPassword, - hardening: TEST_HARDENING_CONFIG, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(BackupErrorTypes.ODIS_RATE_LIMITING_ERROR) - }) - - it('should not rely on ODIS when not included in the configuration', async () => { - mockOdis!.installSignEndpoint(fetchMock, { status: 501 }) - const result = await createBackup({ - data: testData, - userSecret: testPassword, - hardening: { ...TEST_HARDENING_CONFIG, odis: undefined }, - }) - expect(result.ok).toBe(true) - }) - - it('should return a fetch error when the circuit breaker status check fails', async () => { - mockCircuitBreaker!.installStatusEndpoint(fetchMock, { throws: new Error('fetch failed') }) - const result = await createBackup({ - data: testData, - userSecret: testPassword, - hardening: TEST_HARDENING_CONFIG, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.FETCH_ERROR) - }) - - it('should return a service error when the circuit breaker service returns 501', async () => { - mockCircuitBreaker!.installStatusEndpoint(fetchMock, { status: 501 }) - const result = await createBackup({ - data: testData, - userSecret: testPassword, - hardening: TEST_HARDENING_CONFIG, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.SERVICE_ERROR) - }) - - it('should return an unavailable error when the circuit breaker key is destroyed', async () => { - mockCircuitBreaker!.keyStatus = CircuitBreakerKeyStatus.DESTROYED - const result = await createBackup({ - data: testData, - userSecret: testPassword, - hardening: TEST_HARDENING_CONFIG, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.UNAVAILABLE_ERROR) - }) - - it('should not rely on the circuit breaker when not included in the configuration', async () => { - mockCircuitBreaker!.installStatusEndpoint(fetchMock, { status: 501 }) - const result = await createBackup({ - data: testData, - userSecret: testPassword, - hardening: { ...TEST_HARDENING_CONFIG, circuitBreaker: undefined }, - }) - expect(result.ok).toBe(true) - }) -}) - -describe('openBackup', () => { - const testPassword = Buffer.from('backup test password', 'utf8') - const testData = Buffer.from('backup test data', 'utf8') - let testBackup: Backup | undefined - - beforeEach(async () => { - // Create a backup to use for tests of opening below - const testBackupResult = await createBackup({ - data: testData, - userSecret: testPassword, - hardening: TEST_HARDENING_CONFIG, - }) - if (!testBackupResult.ok) { - throw new Error(`failed to create backup for test setup: ${testBackupResult.error}`) - } - testBackup = testBackupResult.result - }) - - it('should result in a decryption error if the encrypted data is modified', async () => { - // Flip a bit in the encrypted data. - // tslint:disable-next-line:no-bitwise - testBackup!.encryptedData[0] ^= 0x01 - const result = await openBackup({ - backup: testBackup!, - userSecret: testPassword, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(BackupErrorTypes.DECRYPTION_ERROR) - }) - - it('should result in a decryption error if odis domain is modified', async () => { - testBackup!.odisDomain!.salt = defined('some salt') - const result = await openBackup({ - backup: testBackup!, - userSecret: testPassword, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(BackupErrorTypes.DECRYPTION_ERROR) - }) - - it('should result in a decryption error if the circuit breaker response changes', async () => { - mockCircuitBreaker!.installUnwrapKeyEndpoint(fetchMock, { - status: 200, - body: { plaintext: Buffer.from('bad fuse key').toString('base64') }, - }) - const result = await openBackup({ - backup: testBackup!, - userSecret: testPassword, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(BackupErrorTypes.DECRYPTION_ERROR) - }) - - it('should result in a decryption error if the computational hardening is altered', async () => { - testBackup!.computationalHardening = { - function: ComputationalHardeningFunction.SCRYPT, - cost: 16, - } - const result = await openBackup({ - backup: testBackup!, - userSecret: testPassword, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(BackupErrorTypes.DECRYPTION_ERROR) - }) - - it('should return a fetch error when request to ODIS fails', async () => { - mockOdis!.installSignEndpoint(fetchMock, { throws: new Error('fetch failed') }) - const result = await openBackup({ - backup: testBackup!, - userSecret: testPassword, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(BackupErrorTypes.FETCH_ERROR) - }) - - it('should return a service error when ODIS returns an error status', async () => { - mockOdis!.installSignEndpoint(fetchMock, { status: 501 }) - const result = await openBackup({ - backup: testBackup!, - userSecret: testPassword, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(BackupErrorTypes.ODIS_SERVICE_ERROR) - }) - - it('should return a rate limit error when ODIS returns a rate limiting status', async () => { - mockOdis!.installSignEndpoint(fetchMock, { status: 429, headers: { 'Retry-After': '60' } }) - const result = await openBackup({ - backup: testBackup!, - userSecret: testPassword, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(BackupErrorTypes.ODIS_RATE_LIMITING_ERROR) - }) - - it('should return a rate limit error when ODIS indicates no quota available', async () => { - mockOdis!.installQuotaEndpoint(fetchMock, { - status: 200, - body: { - success: true, - version: 'mock', - status: { - timer: Date.now() / 1000 + 3600, - counter: 0, - disabled: false, - now: Date.now() / 1000, - }, - }, - }) - const result = await openBackup({ - backup: testBackup!, - userSecret: testPassword, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(BackupErrorTypes.ODIS_RATE_LIMITING_ERROR) - }) - - it('should return a fetch error when circuit breaker unwrap key fails', async () => { - mockCircuitBreaker!.installUnwrapKeyEndpoint(fetchMock, { throws: new Error('fetch failed') }) - const result = await openBackup({ - backup: testBackup!, - userSecret: testPassword, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.FETCH_ERROR) - }) - - it('should return a service error when the circuit breaker service returns 501', async () => { - mockCircuitBreaker!.installUnwrapKeyEndpoint(fetchMock, { status: 501 }) - const result = await openBackup({ - backup: testBackup!, - userSecret: testPassword, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.SERVICE_ERROR) - }) - - it('should return an unavailable error when the circuit breaker key is destroyed', async () => { - mockCircuitBreaker!.keyStatus = CircuitBreakerKeyStatus.DESTROYED - const result = await openBackup({ - backup: testBackup!, - userSecret: testPassword, - }) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.UNAVAILABLE_ERROR) - }) -}) diff --git a/packages/sdk/encrypted-backup/src/backup.ts b/packages/sdk/encrypted-backup/src/backup.ts deleted file mode 100644 index 7ee6f445e6..0000000000 --- a/packages/sdk/encrypted-backup/src/backup.ts +++ /dev/null @@ -1,507 +0,0 @@ -import { eqAddress } from '@celo/base/lib/address' -import { Err, Ok, Result } from '@celo/base/lib/result' -import { - CircuitBreakerClient, - CircuitBreakerKeyStatus, - CircuitBreakerServiceContext, - CircuitBreakerUnavailableError, -} from '@celo/identity/lib/odis/circuit-breaker' -import { ServiceContext as OdisServiceContext } from '@celo/identity/lib/odis/query' -import { SequentialDelayDomain } from '@celo/phone-number-privacy-common/lib/domains' -import * as crypto from 'crypto' -import debugFactory from 'debug' -import { - ComputationalHardeningConfig, - EnvironmentIdentifier, - HardeningConfig, - PASSWORD_HARDENING_ALFAJORES_CONFIG, - PASSWORD_HARDENING_MAINNET_CONFIG, - PIN_HARDENING_ALFAJORES_CONFIG, - PIN_HARDENING_MAINNET_CONFIG, -} from './config' -import { BackupError, InvalidBackupError, UsageError } from './errors' -import { buildOdisDomain, odisHardenKey, odisQueryAuthorizer } from './odis' -import { computationalHardenKey, decrypt, deriveKey, encrypt, KDFInfo } from './utils' - -const debug = debugFactory('kit:encrypted-backup:backup') - -/** - * Backup structure encoding the information needed to implement the encrypted backup protocol. - * - * @remarks The structure below and its related functions implement the encrypted backup protocol - * designed for wallet account backups. More information about the protocol can be found in the - * official {@link https://docs.celo.org/celo-codebase/protocol/identity/encrypted-cloud-backup | - * Celo documentation} - */ -export interface Backup { - /** - * AES-128-GCM encryption of the user's secret backup data. - * - * @remarks The backup key is derived from the user's password or PIN hardened with input from the - * ODIS rate-limited hashing service and optionally a circuit breaker service. - */ - encryptedData: Buffer - - /** - * A randomly chosen 256-bit value. Ensures uniqueness of the password derived encryption key. - * - * @remarks The nonce value is appended to the password for local key derivation. It is also used - * to derive an authentication key to include in the ODIS Domain for domain separation and to - * ensure quota cannot be consumed by parties without access to the backup. - */ - nonce: Buffer - - /** - * ODIS Domain instance to be included in the query to ODIS for password hardening, - * - * @remarks Currently only SequentialDelayDomain is supported. Other ODIS domains intended for key - * hardening may be supported in the future. - */ - odisDomain?: SequentialDelayDomain - - /** - * RSA-OAEP-256 encryption of a randomly chosen 128-bit value, the fuse key. - * - * @remarks The fuse key, if provided, is combined with the password in local key derivation. - * Encryption is under the public key of the circuit breaker service. In order to get the fuseKey - * the client will send this ciphertext to the circuit breaker service for decryption. - */ - encryptedFuseKey?: Buffer - - /** - * Options for local computational hardening of the encryption key through PBKDF or scrypt. - * - * @remarks Adding computational hardening provides a measure of security from password guessing - * when the password has a moderate amount of entropy (e.g. a password generated under good - * guidelines). If the user secret has very low entropy, such as with a 6-digit PIN, - * computational hardening does not add significant security. - */ - computationalHardening?: ComputationalHardeningConfig - - /** Version number for the backup feature. Used to facilitate backwards compatibility. */ - version: string - - /** - * Data provided by the backup creator to identify the backup and its context - * - * @remarks Metadata is provided by, and only meaningful to, the SDK user. The intention is for - * this metadata to be used for identifying the backup and providing any context needed in the - * application - * - * @example - * ```typescript - * { - * // Address of the primary account stored a backup of an account key. Used to display the - * // balance and latest transaction information for a given backup. - * accountAddress: string - * // Unix timestamp used to indicate when the backup was created. - * timestamp: number - * } - * ``` - */ - metadata?: { [key: string]: unknown } - - /** Information including the URL and public keys of the ODIS and circuit breaker services. */ - environment?: { - odis?: OdisServiceContext - circuitBreaker?: CircuitBreakerServiceContext - } -} - -export interface CreatePinEncryptedBackupArgs { - data: Buffer - pin: string - environment?: EnvironmentIdentifier - metadata?: { [key: string]: unknown } -} - -/** - * Create a data backup, encrypting it with a hardened key derived from the given PIN. - * - * @remarks Using a 4 or 6 digit PIN for encryption requires an extremely restrictive rate limit for - * attempts to guess the PIN. This is enforced by ODIS through the SequentialDelayDomain with - * settings to allow the user (or an attacker) only a fixed number of attempts to guess their PIN. - * - * Because PINs have very little entropy, the total number of guesses is very restricted. - * * On the first day, the client has 10 attempts. 5 within 10s. 5 more over roughly 45 minutes. - * * On the second day, the client has 5 attempts over roughly 2 minutes. - * * On the third day, the client has 3 attempts over roughly 40 seconds. - * * On the fourth day, the client has 2 attempts over roughly 10 seconds. - * * Overall, the client has 25 attempts over 4 days. All further attempts will be denied. - * - * It is strongly recommended that the calling application implement a PIN blocklist to prevent the - * user from selecting a number of the most common PIN codes (e.g. blocking the top 25k PINs by - * frequency of appearance in the HIBP Passwords dataset). An example implementation can be seen in - * the Valora wallet. {@link - * https://github.com/valora-inc/wallet/blob/3940661c40d08e4c5db952bd0abeaabb0030fc7a/packages/mobile/src/pincode/authentication.ts#L56-L108 - * | PIN blocklist implementation} - * - * In order to handle the event of an ODIS service compromise, this configuration additionally - * includes a circuit breaker service run by Valora. In the event of an ODIS compromise, the Valora - * team will take their service offline, preventing backups using the circuit breaker from being - * opened. This ensures that an attacker who has compromised ODIS cannot leverage their attack to - * forcibly open backups created with this function. - * - * @param data The secret data (e.g. BIP-39 mnemonic phrase) to be included in the encrypted backup. - * @param pin PIN to use in deriving the encryption key. - * @param hardening Configuration for how the password should be hardened in deriving the key. - * @param metadata Arbitrary key-value data to include in the backup to identify it. - */ -export async function createPinEncryptedBackup({ - data, - pin, - environment, - metadata, -}: CreatePinEncryptedBackupArgs): Promise> { - // Select the hardening configuration based on the environment selector. - let hardening: HardeningConfig | undefined - if (environment === EnvironmentIdentifier.ALFAJORES) { - hardening = PIN_HARDENING_ALFAJORES_CONFIG - } else if (environment === EnvironmentIdentifier.MAINNET || environment === undefined) { - hardening = PIN_HARDENING_MAINNET_CONFIG - } - if (hardening === undefined) { - throw new Error('Implementation error: unhandled environment identifier') - } - - return createBackup({ data, userSecret: pin, hardening, metadata }) -} - -export interface CreatePasswordEncryptedBackupArgs { - data: Buffer - password: string - environment?: EnvironmentIdentifier - metadata?: { [key: string]: unknown } -} - -/** - * Create a data backup, encrypting it with a hardened key derived from the given password. - * - * @remarks Because passwords have moderate entropy, the total number of guesses is restricted. - * * The user initially gets 5 attempts without delay. - * * Then the user gets two attempts every 5 seconds for up to 20 attempts. - * * Then the user gets two attempts every 30 seconds for up to 20 attempts. - * * Then the user gets two attempts every 5 minutes for up to 20 attempts. - * * Then the user gets two attempts every hour for up to 20 attempts. - * * Then the user gets two attempts every day for up to 20 attempts. - * - * Following guidelines in NIST-800-63-3 it is strongly recommended that the caller apply a password - * blocklist to the users choice of password. - * - * In order to handle the event of an ODIS service compromise, this configuration additionally - * hardens the password input with a computational hardening function. In particular, scrypt is used - * with IETF recommended parameters {@link - * https://tools.ietf.org/id/draft-whited-kitten-password-storage-00.html#name-scrypt | IETF - * recommended scrypt parameters } - * - * @param data The secret data (e.g. BIP-39 mnemonic phrase) to be included in the encrypted backup. - * @param password Password to use in deriving the encryption key. - * @param hardening Configuration for how the password should be hardened in deriving the key. - * @param metadata Arbitrary key-value data to include in the backup to identify it. - */ -export async function createPasswordEncryptedBackup({ - data, - password, - environment, - metadata, -}: CreatePasswordEncryptedBackupArgs): Promise> { - // Select the hardening configuration based on the environment selector. - let hardening: HardeningConfig | undefined - if (environment === EnvironmentIdentifier.ALFAJORES) { - hardening = PASSWORD_HARDENING_ALFAJORES_CONFIG - } else if (environment === EnvironmentIdentifier.MAINNET || environment === undefined) { - hardening = PASSWORD_HARDENING_MAINNET_CONFIG - } - if (hardening === undefined) { - throw new Error('Implementation error: unhandled environment identifier') - } - - return createBackup({ data, userSecret: password, hardening, metadata }) -} - -export interface CreateBackupArgs { - data: Buffer - userSecret: Buffer | string - hardening: HardeningConfig - metadata?: { [key: string]: unknown } -} - -/** - * Create a data backup, encrypting it with a hardened key derived from the given password or PIN. - * - * @param data The secret data (e.g. BIP-39 mnemonic phrase) to be included in the encrypted backup. - * @param userSecret Password, PIN, or other user secret to use in deriving the encryption key. - * If a string is provided, it will be UTF-8 encoded into a Buffer before use. - * @param hardening Configuration for how the password should be hardened in deriving the key. - * @param metadata Arbitrary key-value data to include in the backup to identify it. - * - * @privateRemarks Most of this functions code is devoted to key generation starting with the input - * password or PIN and ending up with a hardened encryption key. It is important that the order and - * inputs to each step in the derivation be well considered and implemented correctly. One important - * requirement is that no output included in the backup acts as a "commitment" to the password or PIN - * value, except the final ciphertext. An example of an issue with this would be if a hash of the - * password and nonce were included in the backup. If a commitment to the password or PIN is - * included, an attacker can locally brute force that commitment to recover the password, then use - * that knowledge to complete the derivation. - */ -export async function createBackup({ - data, - userSecret, - hardening, - metadata, -}: CreateBackupArgs): Promise> { - // Password and backup data are not included in any logging as they are likely sensitive. - debug('creating a backup with the following information', hardening, metadata) - - // Safety measure to prevent users from accidentally using this API without any hardening. - if (hardening.odis === undefined && hardening.computational === undefined) { - return Err( - new UsageError(new Error('createBackup cannot be used with a empty hardening config')) - ) - } - - // Generate a 32-byte random nonce for the backup. Use the first half to salt the user secret - // input and the second half to derive an authentication key for making queries to ODIS. - const nonce = crypto.randomBytes(32) - const passwordSalt = nonce.slice(0, 16) - const odisAuthKeySeed = nonce.slice(16, 32) - const userSecretBuffer = - typeof userSecret === 'string' ? Buffer.from(userSecret, 'utf8') : userSecret - const initialKey = deriveKey(KDFInfo.PASSWORD, [userSecretBuffer, passwordSalt]) - - // Generate a fuse key and mix it into the entropy of the key - let encryptedFuseKey: Buffer | undefined - let updatedKey: Buffer - if (hardening.circuitBreaker !== undefined) { - debug('generating a fuse key to enabled use of the circuit breaker service') - const circuitBreakerClient = new CircuitBreakerClient(hardening.circuitBreaker.environment) - - // Check that the circuit breaker is online. Although we do not need to interact with the - // service to create the backup, we should ensure its keys are not disabled or destroyed, - // otherwise we may not be able to open the backup that we create. - // Note that this status check is not strictly necessary and can be removed to all users to - // proceed when the circuit breaker is temporarily unavailable at the risk of not being able to - // open their backup later. - const serviceStatus = await circuitBreakerClient.status() - if (!serviceStatus.ok) { - return Err(serviceStatus.error) - } - if (serviceStatus.result !== CircuitBreakerKeyStatus.ENABLED) { - return Err(new CircuitBreakerUnavailableError(serviceStatus.result)) - } - debug('confirmed that the circuit breaker is online') - - // Generate a fuse key and encrypt it against the circuit breaker public key. - debug('generating and wrapping the fuse key') - const fuseKey = crypto.randomBytes(16) - const wrap = circuitBreakerClient.wrapKey(fuseKey) - if (!wrap.ok) { - return Err(wrap.error) - } - encryptedFuseKey = wrap.result - - // Mix the fuse key into the ongoing key hardening. Note that mixing in the circuit breaker key - // occurs before the request to ODIS. This means an attacker would need to acquire the fuse key - // _before_ they can make any attempts to guess the user's secret. - debug('mixing the fuse key into the keying material') - updatedKey = deriveKey(KDFInfo.FUSE_KEY, [initialKey, fuseKey]) - } else { - debug('not using the circuit breaker service') - updatedKey = initialKey - } - - // Harden the key with the output of a rate limited ODIS POPRF function. - let domain: SequentialDelayDomain | undefined - let odisHardenedKey: Buffer | undefined - if (hardening.odis !== undefined) { - debug('hardening the user key with output from ODIS') - // Derive the query authorizer wallet and address from the nonce, then build the ODIS domain. - // This domain acts as a binding rate limit configuration for ODIS, enforcing that the client must - // know the backup nonce, and can only make the given number of queries. - const authorizer = odisQueryAuthorizer(odisAuthKeySeed) - domain = buildOdisDomain(hardening.odis, authorizer.address) - - debug('sending request to ODIS to harden the backup encryption key') - const odisHardening = await odisHardenKey( - updatedKey, - domain, - hardening.odis.environment, - authorizer.wallet - ) - if (!odisHardening.ok) { - return Err(odisHardening.error) - } - odisHardenedKey = odisHardening.result - } else { - debug('not using ODIS for key hardening') - } - - let computationalHardenedKey: Buffer | undefined - if (hardening.computational !== undefined) { - debug('hardening user key with computational function', hardening.computational) - const computationalHardening = await computationalHardenKey(updatedKey, hardening.computational) - if (!computationalHardening.ok) { - return Err(computationalHardening.error) - } - computationalHardenedKey = computationalHardening.result - } else { - debug('not using computational key hardening') - } - - debug('finalizing encryption key') - const finalKey = deriveKey(KDFInfo.FINALIZE, [ - updatedKey, - odisHardenedKey ?? Buffer.alloc(0), - computationalHardenedKey ?? Buffer.alloc(0), - ]) - - debug('encrypting backup data with final encryption key') - const encryption = encrypt(finalKey, data) - if (!encryption.ok) { - return Err(encryption.error) - } - - // Encrypted and wrap the data in a Backup structure. - debug('created encrypted backup') - return Ok({ - encryptedData: encryption.result, - nonce, - odisDomain: domain, - encryptedFuseKey, - computationalHardening: hardening.computational, - // TODO(victor): Bump this to 1.0 when the final crypto is added. - version: '0.0.1', - metadata, - environment: { - odis: hardening.odis?.environment, - circuitBreaker: hardening.circuitBreaker?.environment, - }, - }) -} - -export interface OpenBackupArgs { - backup: Backup - userSecret: Buffer | string -} - -/** - * Open an encrypted backup file, using the provided password or PIN to derive the decryption key. - * - * @param backup Backup structure including the ciphertext and key derivation information. - * @param userSecret Password, PIN, or other user secret to use in deriving the encryption key. - * If a string is provided, it will be UTF-8 encoded into a Buffer before use. - */ -export async function openBackup({ - backup, - userSecret, -}: OpenBackupArgs): Promise> { - debug('opening an encrypted backup') - - // Split the nonce into the two halves for password salting and auth key generation respectively. - const passwordSalt = backup.nonce.slice(0, 16) - const odisAuthKeySeed = backup.nonce.slice(16, 32) - const userSecretBuffer = - typeof userSecret === 'string' ? Buffer.from(userSecret, 'utf8') : userSecret - const initialKey = deriveKey(KDFInfo.PASSWORD, [userSecretBuffer, passwordSalt]) - - // If a circuit breaker is in use, request a decryption of the fuse key and mix it in. - let updatedKey: Buffer - if (backup.encryptedFuseKey !== undefined) { - if (backup.environment?.circuitBreaker === undefined) { - return Err( - new InvalidBackupError( - new Error('encrypted fuse key is provided but no circuit breaker environment is provided') - ) - ) - } - const circuitBreakerClient = new CircuitBreakerClient(backup.environment.circuitBreaker) - - debug( - 'requesting the circuit breaker service unwrap the encrypted circuit breaker key', - backup.environment.circuitBreaker - ) - const unwrap = await circuitBreakerClient.unwrapKey(backup.encryptedFuseKey) - if (!unwrap.ok) { - return Err(unwrap.error) - } - - // Mix the fuse key into the ongoing key hardening. Note that mixing in the circuit breaker key - // occurs before the request to ODIS. This means an attacker would need to aquire the fuse key - // _before_ they can make any attempts to guess the user's secret. - updatedKey = deriveKey(KDFInfo.FUSE_KEY, [initialKey, unwrap.result]) - } else { - debug('backup did not specify an encrypted fuse key') - updatedKey = initialKey - } - - // If ODIS is in use, harden the key with the output of a rate limited ODIS POPRF function. - let odisHardenedKey: Buffer | undefined - if (backup.odisDomain !== undefined) { - const domain = backup.odisDomain - - // Derive the query authorizer wallet and address from the nonce. - // If the ODIS domain is authenticated, the authorizer address should match the domain. - const authorizer = odisQueryAuthorizer(odisAuthKeySeed) - if (domain.address.defined && !eqAddress(authorizer.address, domain.address.value)) { - return Err( - new InvalidBackupError( - new Error( - 'domain query authorizer address is provided but is not derived from the backup nonce' - ) - ) - ) - } - if (backup.environment?.odis === undefined) { - return Err( - new InvalidBackupError( - new Error('ODIS domain is provided by no ODIS environment information') - ) - ) - } - - debug('requesting a key hardening response from ODIS') - const odisHardening = await odisHardenKey( - updatedKey, - domain, - backup.environment.odis, - authorizer.wallet - ) - if (!odisHardening.ok) { - return Err(odisHardening.error) - } - odisHardenedKey = odisHardening.result - } else { - debug('not using ODIS for key hardening') - } - - let computationalHardenedKey: Buffer | undefined - if (backup.computationalHardening !== undefined) { - debug('hardening user key with computational function', backup.computationalHardening) - const computationalHardening = await computationalHardenKey( - updatedKey, - backup.computationalHardening - ) - if (!computationalHardening.ok) { - return Err(computationalHardening.error) - } - computationalHardenedKey = computationalHardening.result - } else { - debug('not using computational key hardening') - } - - debug('finalizing decryption key') - const finalKey = deriveKey(KDFInfo.FINALIZE, [ - updatedKey, - odisHardenedKey ?? Buffer.alloc(0), - computationalHardenedKey ?? Buffer.alloc(0), - ]) - - debug('decrypting backup with finalized decryption key') - const decryption = decrypt(finalKey, backup.encryptedData) - if (!decryption.ok) { - return Err(decryption.error) - } - - debug('decrypted backup') - return Ok(decryption.result) -} diff --git a/packages/sdk/encrypted-backup/src/config.ts b/packages/sdk/encrypted-backup/src/config.ts deleted file mode 100644 index edc1944943..0000000000 --- a/packages/sdk/encrypted-backup/src/config.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { - CircuitBreakerServiceContext, - VALORA_ALFAJORES_CIRCUIT_BREAKER_ENVIRONMENT, - VALORA_MAINNET_CIRCUIT_BREAKER_ENVIRONMENT, -} from '@celo/identity/lib/odis/circuit-breaker' -import { - ODIS_ALFAJORES_CONTEXT_DOMAINS, - ODIS_MAINNET_CONTEXT_DOMAINS, - ServiceContext as OdisServiceContext, -} from '@celo/identity/lib/odis/query' -import { SequentialDelayStage } from '@celo/phone-number-privacy-common' -import { defined, noNumber } from '@celo/utils/lib/sign-typed-data-utils' -import { ScryptOptions } from './utils' - -export interface HardeningConfig { - /** - * If provided, a computational hardening function (e.g. scrypt or PBKDF2) will be applied to - * locally harden the backup encryption key. - * - * @remarks Recommended for password-encrypted backups, especially if a circuit breaker is not in - * use, as this provides some degree of protection in the event of an ODIS compromise. When - * generating backups on low-power devices (e.g. budget smart phones) and encrypting with - * low-entropy secrets (e.g. 6-digit PINs) local hardening cannot offer significant protection. - */ - computational?: ComputationalHardeningConfig - - /** If provided, ODIS will be used with the given configuration to harden the backup key */ - odis?: OdisHardeningConfig - - /** - * If provided, a circuit breaker will be used with the given configuration to protect the backup key - */ - circuitBreaker?: CircuitBreakerConfig -} - -/** Configuration for usage of ODIS to harden the encryption keys */ -export interface OdisHardeningConfig { - /** - * Rate limiting information used to construct the ODIS domain which will be used to harden the - * encryption key through ODIS' domain password hardening service. - * - * @remarks Currently supports the SequentialDelayDomain. In the future, as additional domains are - * standardized for key hardening, they may be added here to allow a wider range of configuration. - */ - rateLimit: SequentialDelayStage[] - - /** Environment information including the URL and public key of the ODIS service */ - environment: OdisServiceContext -} - -/** Configuration for usage of a circuit breaker to protect the encryption keys */ -export interface CircuitBreakerConfig { - /** Environment information including the URL and public key of the circuit breaker service */ - environment: CircuitBreakerServiceContext -} - -export enum ComputationalHardeningFunction { - PBKDF = 'pbkdf2_sha256', - SCRYPT = 'scrypt', -} - -export interface PbkdfConfig { - function: ComputationalHardeningFunction.PBKDF - iterations: number -} - -export interface ScryptConfig extends ScryptOptions { - function: ComputationalHardeningFunction.SCRYPT -} - -export type ComputationalHardeningConfig = PbkdfConfig | ScryptConfig - -/** - * ODIS SequentialDelayDomain rate limit configured to be appropriate for hardening a 6-digit PIN. - * - * @remarks Because PINs have very little entropy, the total number of guesses is very restricted. - * * On the first day, the client has 10 attempts. 5 within 10s. 5 more over roughly 45 minutes. - * * On the second day, the client has 5 attempts over roughly 2 minutes. - * * On the third day, the client has 3 attempts over roughly 40 seconds. - * * On the fourth day, the client has 2 attempts over roughly 10 seconds. - * * Overall, the client has 20 attempts over 4 days. All further attempts will be denied. - */ -const PIN_HARDENING_RATE_LIMIT: SequentialDelayStage[] = [ - // First stage is setup, as the user will need to make a single query to create their backup. - { - delay: 0, - resetTimer: defined(true), - batchSize: defined(1), - repetitions: noNumber, - }, - // On the first day, the client has 10 attempts. 5 within 10s. 5 more over roughly 45 minutes. - { - delay: 0, - resetTimer: defined(true), - batchSize: defined(3), - repetitions: noNumber, - }, - { - delay: 10, - resetTimer: defined(true), - batchSize: defined(2), - repetitions: noNumber, - }, - { - delay: 30, - resetTimer: defined(false), - batchSize: defined(1), - repetitions: noNumber, - }, - { - delay: 60, - resetTimer: defined(false), - batchSize: defined(1), - repetitions: noNumber, - }, - { - delay: 300, - resetTimer: defined(false), - batchSize: defined(1), - repetitions: noNumber, - }, - { - delay: 900, - resetTimer: defined(false), - batchSize: defined(1), - repetitions: noNumber, - }, - { - delay: 1800, - resetTimer: defined(true), - batchSize: defined(1), - repetitions: noNumber, - }, - // On the second day, the client has 5 attempts over roughly 2 minutes. - { - delay: 86400, - resetTimer: defined(true), - batchSize: defined(2), - repetitions: noNumber, - }, - { - delay: 10, - resetTimer: defined(false), - batchSize: defined(1), - repetitions: noNumber, - }, - { - delay: 30, - resetTimer: defined(false), - batchSize: defined(1), - repetitions: noNumber, - }, - { - delay: 60, - resetTimer: defined(true), - batchSize: defined(1), - repetitions: noNumber, - }, - // On the third day, the client has 3 attempts over roughly 40 seconds. - { - delay: 86400, - resetTimer: defined(true), - batchSize: defined(1), - repetitions: noNumber, - }, - { - delay: 10, - resetTimer: defined(false), - batchSize: defined(1), - repetitions: noNumber, - }, - { - delay: 30, - resetTimer: defined(true), - batchSize: defined(1), - repetitions: noNumber, - }, - // On the fourth day, the client has 2 attempts over roughly 10 seconds. - { - delay: 86400, - resetTimer: defined(true), - batchSize: defined(1), - repetitions: noNumber, - }, - { - delay: 10, - resetTimer: defined(false), - batchSize: defined(1), - repetitions: noNumber, - }, -] - -/** - * ODIS SequentialDelayDomain rate limit configured to be appropriate for hardening a password. - * - * @remarks Because passwords have moderate entropy, the total number of guesses is restricted. - * * The user initially gets 5 attempts without delay. - * * Then the user gets two attempts every 5 seconds for up to 20 attempts. - * * Then the user gets two attempts every 30 seconds for up to 20 attempts. - * * Then the user gets two attempts every 5 minutes for up to 20 attempts. - * * Then the user gets two attempts every hour for up to 20 attempts. - * * Then the user gets two attempts every day for up to 20 attempts. - */ -const PASSWORD_HARDENING_RATE_LIMIT: SequentialDelayStage[] = [ - // First stage is setup, as the user will need to make a single query to create their backup. - { - delay: 0, - resetTimer: defined(true), - batchSize: defined(1), - repetitions: noNumber, - }, - // After the first 5 attempts, the user has 100 attempts with the delays increasing every 20. - { - delay: 0, - resetTimer: defined(true), - batchSize: defined(5), - repetitions: noNumber, - }, - { - delay: 5, - resetTimer: defined(true), - batchSize: defined(2), - repetitions: defined(10), - }, - { - delay: 30, - resetTimer: defined(true), - batchSize: defined(2), - repetitions: defined(10), - }, - { - delay: 300, - resetTimer: defined(true), - batchSize: defined(2), - repetitions: defined(10), - }, - { - delay: 3600, - resetTimer: defined(true), - batchSize: defined(2), - repetitions: defined(10), - }, - { - delay: 86400, - resetTimer: defined(true), - batchSize: defined(2), - repetitions: defined(10), - }, -] - -/** - * ODIS SequentialDelayDomain rate limit configured for e2e testing where no rate limit should be applied. - * - * @remarks This should only be used testing purposes - */ -const E2E_TESTING_RATE_LIMIT: SequentialDelayStage[] = [ - { - delay: 0, - resetTimer: defined(true), - batchSize: defined(1000000000), - repetitions: defined(1000000000), - }, -] - -/** - * ODIS SequentialDelayDomain rate limit configured for e2e testing where the user should have no quota. - * - * @remarks This should only be used testing purposes - */ -const NO_QUOTA_RATE_LIMIT: SequentialDelayStage[] = [ - { - delay: 0, - resetTimer: defined(true), - batchSize: defined(0), - repetitions: defined(0), - }, -] - -export enum EnvironmentIdentifier { - MAINNET = 'MAINNET', - ALFAJORES = 'ALFAJORES', -} - -export const PIN_HARDENING_MAINNET_CONFIG: HardeningConfig = { - odis: { - rateLimit: PIN_HARDENING_RATE_LIMIT, - environment: ODIS_MAINNET_CONTEXT_DOMAINS, - }, - circuitBreaker: { - environment: VALORA_MAINNET_CIRCUIT_BREAKER_ENVIRONMENT, - }, -} - -export const PIN_HARDENING_ALFAJORES_CONFIG: HardeningConfig = { - odis: { - rateLimit: PIN_HARDENING_RATE_LIMIT, - environment: ODIS_ALFAJORES_CONTEXT_DOMAINS, - }, - circuitBreaker: { - environment: VALORA_ALFAJORES_CIRCUIT_BREAKER_ENVIRONMENT, - }, -} - -export const PASSWORD_HARDENING_MAINNET_CONFIG: HardeningConfig = { - odis: { - rateLimit: PASSWORD_HARDENING_RATE_LIMIT, - environment: ODIS_MAINNET_CONTEXT_DOMAINS, - }, - computational: { - function: ComputationalHardeningFunction.SCRYPT, - cost: 32768, - blockSize: 8, - parallelization: 1, - }, -} - -export const E2E_TESTING_MAINNET_CONFIG: HardeningConfig = { - odis: { - rateLimit: E2E_TESTING_RATE_LIMIT, - environment: ODIS_MAINNET_CONTEXT_DOMAINS, - }, - computational: { - function: ComputationalHardeningFunction.SCRYPT, - cost: 32768, - blockSize: 8, - parallelization: 1, - }, -} - -export const NO_QUOTA_MAINNET_CONFIG: HardeningConfig = { - odis: { - rateLimit: NO_QUOTA_RATE_LIMIT, - environment: ODIS_MAINNET_CONTEXT_DOMAINS, - }, - computational: { - function: ComputationalHardeningFunction.SCRYPT, - cost: 32768, - blockSize: 8, - parallelization: 1, - }, -} - -export const PASSWORD_HARDENING_ALFAJORES_CONFIG: HardeningConfig = { - odis: { - rateLimit: PASSWORD_HARDENING_RATE_LIMIT, - environment: ODIS_ALFAJORES_CONTEXT_DOMAINS, - }, - computational: { - function: ComputationalHardeningFunction.SCRYPT, - cost: 32768, - blockSize: 8, - parallelization: 1, - }, -} - -export const E2E_TESTING_ALFAJORES_CONFIG: HardeningConfig = { - odis: { - rateLimit: E2E_TESTING_RATE_LIMIT, - environment: ODIS_ALFAJORES_CONTEXT_DOMAINS, - }, - computational: { - function: ComputationalHardeningFunction.SCRYPT, - cost: 32768, - blockSize: 8, - parallelization: 1, - }, -} - -export const NO_QUOTA_ALFAJORES_CONFIG: HardeningConfig = { - odis: { - rateLimit: NO_QUOTA_RATE_LIMIT, - environment: ODIS_ALFAJORES_CONTEXT_DOMAINS, - }, - computational: { - function: ComputationalHardeningFunction.SCRYPT, - cost: 32768, - blockSize: 8, - parallelization: 1, - }, -} diff --git a/packages/sdk/encrypted-backup/src/errors.ts b/packages/sdk/encrypted-backup/src/errors.ts deleted file mode 100644 index 9a348e92f9..0000000000 --- a/packages/sdk/encrypted-backup/src/errors.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { RootError } from '@celo/base/lib/result' -import { CircuitBreakerError } from '@celo/identity/lib/odis/circuit-breaker' -import { ScryptOptions } from './utils' - -export enum BackupErrorTypes { - AUTHORIZATION_ERROR = 'AUTHORIZATION_ERROR', - DECODE_ERROR = 'DECODE_ERROR', - DECRYPTION_ERROR = 'DECRYPTION_ERROR', - ENCRYPTION_ERROR = 'ENCRYPTION_ERROR', - FETCH_ERROR = 'FETCH_ERROR', - INVALID_BACKUP_ERROR = 'INVALID_BACKUP_ERROR', - ODIS_SERVICE_ERROR = 'ODIS_SERVICE_ERROR', - ODIS_RATE_LIMITING_ERROR = 'ODIS_RATE_LIMITING_ERROR', - ODIS_VERIFICATION_ERROR = 'ODIS_VERIFICATION_ERROR', - PBKDF_ERROR = 'PBKDF_ERROR', - SCRYPT_ERROR = 'SCRYPT_ERROR', - USAGE_ERROR = 'USAGE_ERROR', -} - -export class AuthorizationError extends RootError { - constructor(readonly error?: Error) { - super(BackupErrorTypes.AUTHORIZATION_ERROR) - } -} - -export class DecodeError extends RootError { - constructor(readonly error?: Error) { - super(BackupErrorTypes.DECODE_ERROR) - } -} - -export class DecryptionError extends RootError { - constructor(readonly error?: Error) { - super(BackupErrorTypes.DECRYPTION_ERROR) - } -} - -export class EncryptionError extends RootError { - constructor(readonly error?: Error) { - super(BackupErrorTypes.ENCRYPTION_ERROR) - } -} - -export class FetchError extends RootError { - constructor(readonly error?: Error) { - super(BackupErrorTypes.FETCH_ERROR) - } -} - -export class InvalidBackupError extends RootError { - constructor(readonly error?: Error) { - super(BackupErrorTypes.INVALID_BACKUP_ERROR) - } -} - -export class OdisServiceError extends RootError { - constructor(readonly error?: Error, readonly version?: string) { - super(BackupErrorTypes.ODIS_SERVICE_ERROR) - } -} - -export class OdisRateLimitingError extends RootError { - constructor(readonly notBefore?: number, readonly error?: Error) { - super(BackupErrorTypes.ODIS_RATE_LIMITING_ERROR) - } -} - -export class OdisVerificationError extends RootError { - constructor(readonly error?: Error) { - super(BackupErrorTypes.ODIS_VERIFICATION_ERROR) - } -} - -export class PbkdfError extends RootError { - constructor(readonly iterations: number, readonly error?: Error) { - super(BackupErrorTypes.PBKDF_ERROR) - } -} - -export class ScryptError extends RootError { - constructor(readonly options: ScryptOptions, readonly error?: Error) { - super(BackupErrorTypes.SCRYPT_ERROR) - } -} - -export class UsageError extends RootError { - constructor(readonly error?: Error) { - super(BackupErrorTypes.USAGE_ERROR) - } -} - -export type BackupError = - | AuthorizationError - | CircuitBreakerError - | DecodeError - | DecryptionError - | EncryptionError - | FetchError - | InvalidBackupError - | OdisServiceError - | OdisRateLimitingError - | OdisVerificationError - | PbkdfError - | ScryptError - | UsageError diff --git a/packages/sdk/encrypted-backup/src/globals.d.ts b/packages/sdk/encrypted-backup/src/globals.d.ts deleted file mode 100644 index 0318570a84..0000000000 --- a/packages/sdk/encrypted-backup/src/globals.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare const fetchMock diff --git a/packages/sdk/encrypted-backup/src/index.ts b/packages/sdk/encrypted-backup/src/index.ts deleted file mode 100644 index 24d4b9f2df..0000000000 --- a/packages/sdk/encrypted-backup/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './backup' -export * from './config' -export * from './errors' -export * from './odis' -export * from './schema' diff --git a/packages/sdk/encrypted-backup/src/odis.mock.ts b/packages/sdk/encrypted-backup/src/odis.mock.ts deleted file mode 100644 index 1416449188..0000000000 --- a/packages/sdk/encrypted-backup/src/odis.mock.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { ServiceContext as OdisServiceContext } from '@celo/identity/lib/odis/query' -import { - checkSequentialDelayRateLimit, - DomainEndpoint, - domainHash, - DomainQuotaStatusRequest, - DomainQuotaStatusResponse, - DomainRestrictedSignatureRequest, - DomainRestrictedSignatureResponse, - PoprfServer, - SequentialDelayDomain, - SequentialDelayDomainState, - verifyDomainQuotaStatusRequestAuthenticity, - verifyDomainRestrictedSignatureRequestAuthenticity, -} from '@celo/phone-number-privacy-common' -import * as poprf from '@celo/poprf' -import debugFactory from 'debug' - -const debug = debugFactory('kit:encrypted-backup:odis:mock') - -const MOCK_ODIS_KEYPAIR = poprf.keygen(Buffer.from('MOCK ODIS KEYPAIR SEED')) - -export const MOCK_ODIS_ENVIRONMENT: OdisServiceContext = { - odisUrl: 'https://mockodis.com', - odisPubKey: Buffer.from(MOCK_ODIS_KEYPAIR.publicKey).toString('base64'), -} - -export class MockOdis { - static readonly environment = MOCK_ODIS_ENVIRONMENT - - readonly state: Record = {} - readonly poprf = new PoprfServer(MOCK_ODIS_KEYPAIR.privateKey) - - private now = () => Math.floor(Date.now() / 1000) - - private domainState(hash: Buffer) { - return ( - this.state[hash.toString('hex')] ?? { timer: 0, counter: 0, disabled: false, now: this.now() } - ) - } - - quota(req: DomainQuotaStatusRequest): { - status: number - body: DomainQuotaStatusResponse - } { - const authorized = verifyDomainQuotaStatusRequestAuthenticity(req) - if (!authorized) { - return { - status: 401, - body: { - success: false, - version: 'mock', - error: 'unauthorized', - }, - } - } - - return { - status: 200, - body: { - success: true, - version: 'mock', - status: this.domainState(domainHash(req.domain)), - }, - } - } - - sign(req: DomainRestrictedSignatureRequest): { - status: number - body: DomainRestrictedSignatureResponse - } { - const authorized = verifyDomainRestrictedSignatureRequestAuthenticity(req) - if (!authorized) { - return { - status: 401, - body: { - success: false, - version: 'mock', - error: 'unauthorized', - status: undefined, - }, - } - } - - const hash = domainHash(req.domain) - const domainState = this.domainState(hash) - const nonce = req.options.nonce.defined ? req.options.nonce.value : undefined - if (nonce !== domainState.counter) { - return { - status: 403, - body: { - success: false, - version: 'mock', - error: 'incorrect nonce', - status: domainState, - }, - } - } - - const limitCheck = checkSequentialDelayRateLimit(req.domain, this.now(), domainState) - if (!limitCheck.accepted || limitCheck.state === undefined) { - return { - status: 429, - body: { - success: false, - version: 'mock', - error: 'request limit exceeded', - status: domainState, - }, - } - } - this.state[hash.toString('hex')] = limitCheck.state - - let signature: string - try { - signature = this.poprf - .blindEval(hash, Buffer.from(req.blindedMessage, 'base64')) - .toString('base64') - } catch (error) { - return { - // TODO(victor) Note that although this is a returned as a 500, the fault my actually be the - // users because the blinded message is not validated in JS before attempting the evaluation. - // This logic is the same in the real service. When validation functions are added to the - // WASM interface for the POPRF, this can be improved. - status: 500, - body: { - success: false, - version: 'mock', - status: undefined, - error: (error as Error).toString(), - }, - } - } - - return { - status: 200, - body: { - success: true, - version: 'mock', - status: limitCheck.state, - signature, - }, - } - } - - installQuotaEndpoint(mock: typeof fetchMock, override?: any) { - mock.mock( - { - url: new URL(DomainEndpoint.DOMAIN_QUOTA_STATUS, MockOdis.environment.odisUrl).href, - method: 'POST', - }, - override ?? - ((url: string, req: { body: string }) => { - const res = this.quota( - JSON.parse(req.body) as DomainQuotaStatusRequest - ) - debug('Mocking request', JSON.stringify({ url, req, res })) - return res - }) - ) - } - - installSignEndpoint(mock: typeof fetchMock, override?: any) { - mock.mock( - { - url: new URL(DomainEndpoint.DOMAIN_SIGN, MockOdis.environment.odisUrl).href, - method: 'POST', - }, - override ?? - ((url: string, req: { body: string }) => { - const res = this.sign( - JSON.parse(req.body) as DomainRestrictedSignatureRequest - ) - debug('Mocking request', JSON.stringify({ url, req, res })) - return res - }) - ) - } - - install(mock: typeof fetchMock) { - this.installQuotaEndpoint(mock) - this.installSignEndpoint(mock) - } -} diff --git a/packages/sdk/encrypted-backup/src/odis.ts b/packages/sdk/encrypted-backup/src/odis.ts deleted file mode 100644 index e7630ebf73..0000000000 --- a/packages/sdk/encrypted-backup/src/odis.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { Address } from '@celo/base/lib/address' -import { Err, Ok, Result } from '@celo/base/lib/result' -import { - ErrorMessages, - sendOdisDomainRequest, - ServiceContext as OdisServiceContext, -} from '@celo/identity/lib/odis/query' -import { - checkSequentialDelayRateLimit, - DomainEndpoint, - domainHash, - DomainIdentifiers, - DomainQuotaStatusRequest, - domainQuotaStatusRequestEIP712, - DomainQuotaStatusResponse, - domainQuotaStatusResponseSchema, - DomainQuotaStatusResponseSuccess, - DomainRequestTypeTag, - DomainRestrictedSignatureRequest, - domainRestrictedSignatureRequestEIP712, - DomainRestrictedSignatureResponse, - domainRestrictedSignatureResponseSchema, - DomainRestrictedSignatureResponseSuccess, - genSessionID, - PoprfClient, - SequentialDelayDomain, - SequentialDelayDomainStateSchema, -} from '@celo/phone-number-privacy-common' -import { defined, noNumber, noString } from '@celo/utils/lib/sign-typed-data-utils' -import { LocalWallet } from '@celo/wallet-local' -import * as crypto from 'crypto' -import { OdisHardeningConfig } from './config' -import { - AuthorizationError, - BackupError, - FetchError, - OdisRateLimitingError, - OdisServiceError, - OdisVerificationError, - UsageError, -} from './errors' -import { deriveKey, EIP712Wallet, KDFInfo } from './utils' - -/** - * Builds an ODIS SequentialDelayDomain with the given hardening configuration. - * - * @param authorizer Address of the key that should authorize requests to ODIS. - * @returns A SequentialDelayDomain with the provided rate limiting configuration. - */ -export function buildOdisDomain( - config: OdisHardeningConfig, - authorizer: Address, - salt?: string -): SequentialDelayDomain { - return { - name: DomainIdentifiers.SequentialDelay, - version: '1', - stages: config.rateLimit, - address: defined(authorizer), - salt: salt ? defined(salt) : noString, - } -} - -/** - * Returns a hardened key derived from the input key material and a POPRF evaluation on that keying - * material under the given rate limiting domain. - * - * @param key Input key material which will be the blinded input to the ODIS POPRF. - * @param domain Rate limiting configuration and domain input to the ODIS POPRF. - * @param environment Information for the targeted ODIS environment. - * @param wallet Wallet with access to the authorizer signing key specified in the domain input. - * Should be provided if the input domain is authenticated. - */ -export async function odisHardenKey( - key: Buffer, - domain: SequentialDelayDomain, - environment: OdisServiceContext, - wallet?: EIP712Wallet -): Promise> { - // Session ID for logging requests. - const sessionID = genSessionID() - - // Request the quota status for the domain to get the state, including the quota counter. - const quotaResp = await requestOdisDomainQuotaStatus(domain, environment, sessionID, wallet) - if (!quotaResp.ok) { - return quotaResp - } - - // Check locally whether or not we should expect to be able to make a query to ODIS right now. - // Note that this uses the servers timestamp through the `quotaState.now` field. This is because - // mobile clients may have a large clock drift. This prevents that clock drift from resulting in - // misinterpretations of the domain quota. - const quotaState = quotaResp.result.status - const quotaResult = checkSequentialDelayRateLimit( - domain, - // Use the local clock as a fallback. Divide by 1000 to get seconds from ms. - quotaState.now ?? Date.now() / 1000, - quotaState - ) - if (!quotaResult.accepted) { - return Err( - new OdisRateLimitingError( - quotaResult.notBefore, - new Error('client does not currently have quota based on status response.') - ) - ) - } - - // Instantiate a blinding client and blind the key derived from the users password to be hardened. - // NOTE: We do not include a response aggregation step here because it is assumed that we are - // talking to the combiner service, as opposed to talking directly to the signers. - const blindingSeed = crypto.randomBytes(16) - const poprfClient = new PoprfClient( - Buffer.from(environment.odisPubKey, 'base64'), - domainHash(domain), - key, - blindingSeed - ) - - // Request the partial oblivious signature from ODIS. - // Note that making this request will, if successful, result in quota being used in the domain. - const signatureResp = await requestOdisDomainSignature( - poprfClient.blindedMessage.toString('base64'), - quotaState.counter, - domain, - environment, - sessionID, - wallet - ) - if (!signatureResp.ok) { - return signatureResp - } - - // Unblind the signature response received from ODIS to get the POPRF output. - let odisOutput: Buffer - try { - odisOutput = await poprfClient.unblindResponse( - Buffer.from(signatureResp.result.signature, 'base64') - ) - } catch (error) { - return Err(new OdisVerificationError(error as Error)) - } - - // Mix the key with the output from ODIS to get the hardened key. - return Ok(deriveKey(KDFInfo.ODIS_KEY_HARDENING, [key, odisOutput])) -} - -/** - * Derive from the nonce a private key and use it to instantiate a wallet for request signing - * - * @remarks It is important that the auth key does not mix in entropy from the password value. If - * it did, then the derived address and signatures would act as a commitment to the underlying - * password value and would allow offline brute force attacks when combined with the other values - * mixed into the key value. - */ -export function odisQueryAuthorizer(nonce: Buffer): { address: Address; wallet: EIP712Wallet } { - // Derive the domain's request authorization key from the backup nonce. - const authKey = deriveKey(KDFInfo.ODIS_AUTH_KEY, [nonce]) - const wallet = new LocalWallet() - wallet.addAccount(authKey.toString('hex')) - const address = wallet.getAccounts()[0] - if (address === undefined) { - // Throw the error instead if returning it as this is more akin to a panic. - throw new Error('Implementation error: LocalWallet with an added account returned no accounts') - } - return { address, wallet } -} - -/** - * Returns a hardened key derived from the input key material and a POPRF evaluation on that keying - * material under the given rate limiting domain. - * - * @param domain Rate limiting configuration and domain input to the ODIS POPRF. - * @param environment Information for the targeted ODIS environment. - * @param sessionID client-defined session ID for tracking requests across services - * @param wallet Wallet with access to the authorizer signing key specified in the domain input. - * Should be provided if the input domain is authenticated. - */ -export async function requestOdisDomainQuotaStatus( - domain: SequentialDelayDomain, - environment: OdisServiceContext, - sessionID: string, - wallet?: EIP712Wallet -): Promise> { - const quotaStatusReq: DomainQuotaStatusRequest = { - type: DomainRequestTypeTag.QUOTA, - domain, - options: { - signature: noString, - nonce: noNumber, - }, - sessionID: defined(sessionID), - } - - // If a query authorizer is defined in the domain, include a signature over the request. - const authorizer = domain.address.defined ? domain.address.value : undefined - if (authorizer !== undefined) { - if (wallet === undefined || !wallet.hasAccount(authorizer)) { - return Err( - new AuthorizationError( - new Error('key for signing ODIS quota status request is unavailable') - ) - ) - } - quotaStatusReq.options.signature = defined( - await wallet.signTypedData(authorizer, domainQuotaStatusRequestEIP712(quotaStatusReq)) - ) - } else if (wallet !== undefined) { - return Err(new UsageError(new Error('wallet provided but the domain is unauthenticated'))) - } - - let quotaResp: DomainQuotaStatusResponse - try { - quotaResp = await sendOdisDomainRequest( - quotaStatusReq, - environment, - DomainEndpoint.DOMAIN_QUOTA_STATUS, - domainQuotaStatusResponseSchema(SequentialDelayDomainStateSchema) - ) - } catch (error) { - if ((error as Error).message?.includes(ErrorMessages.ODIS_FETCH_ERROR)) { - return Err(new FetchError(error as Error)) - } - return Err(new OdisServiceError(error as Error)) - } - if (!quotaResp.success) { - return Err(new OdisServiceError(new Error(quotaResp.error), quotaResp.version)) - } - - return Ok(quotaResp) -} - -async function requestOdisDomainSignature( - blindedMessage: string, - counter: number, - domain: SequentialDelayDomain, - environment: OdisServiceContext, - sessionID: string, - wallet?: EIP712Wallet -): Promise> { - const signatureReq: DomainRestrictedSignatureRequest = { - type: DomainRequestTypeTag.SIGN, - domain, - options: { - signature: noString, - nonce: defined(counter), - }, - blindedMessage, - sessionID: defined(sessionID), - } - - // If a query authorizer is defined in the domain, include a siganture over the request. - const authorizer = domain.address.defined ? domain.address.value : undefined - if (authorizer !== undefined) { - if (wallet === undefined || !wallet.hasAccount(authorizer)) { - return Err( - new AuthorizationError( - new Error('key for signing ODIS domain signature request is unavailable') - ) - ) - } - signatureReq.options.signature = defined( - await wallet.signTypedData(authorizer, domainRestrictedSignatureRequestEIP712(signatureReq)) - ) - } else if (wallet !== undefined) { - return Err(new UsageError(new Error('wallet provided but the domain is unauthenticated'))) - } - - let signatureResp: DomainRestrictedSignatureResponse - try { - signatureResp = await sendOdisDomainRequest( - signatureReq, - environment, - DomainEndpoint.DOMAIN_SIGN, - domainRestrictedSignatureResponseSchema(SequentialDelayDomainStateSchema) - ) - } catch (error) { - if ((error as Error).message?.includes(ErrorMessages.ODIS_FETCH_ERROR)) { - return Err(new FetchError(error as Error)) - } - if ((error as Error).message?.includes(ErrorMessages.ODIS_RATE_LIMIT_ERROR)) { - return Err(new OdisRateLimitingError(undefined, error as Error)) - } - return Err(new OdisServiceError(error as Error)) - } - if (!signatureResp.success) { - return Err(new OdisServiceError(new Error(signatureResp.error), signatureResp.version)) - } - - return Ok(signatureResp) -} diff --git a/packages/sdk/encrypted-backup/src/schema.ts b/packages/sdk/encrypted-backup/src/schema.ts deleted file mode 100644 index 182da1a1a6..0000000000 --- a/packages/sdk/encrypted-backup/src/schema.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Err, Ok, parseJsonAsResult, Result } from '@celo/base/lib/result' -import { SequentialDelayDomainSchema } from '@celo/phone-number-privacy-common/lib/domains' -import { chain, isLeft } from 'fp-ts/lib/Either' -import { pipe } from 'fp-ts/lib/pipeable' -import * as t from 'io-ts' -import { Backup } from './backup' -import { ComputationalHardeningFunction } from './config' -import { DecodeError } from './errors' - -const BASE64_REGEXP = /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/ - -/** Utility type to leverage io-ts for encoding and decoding of buffers from base64 strings. */ -export const BufferFromBase64 = new t.Type( - 'BufferFromBase64', - Buffer.isBuffer, - (unk: unknown, context: t.Context) => - pipe( - t.string.validate(unk, context), - chain((str: string) => { - // Check that the string is base64 data and return the decoding if it is. - if (!BASE64_REGEXP.test(str)) { - return t.failure(unk, context, 'provided string is not base64') - } - return t.success(Buffer.from(str, 'base64')) - }) - ), - (buffer: Buffer) => buffer.toString('base64') -) - -/** io-ts codec used to encode and decode backups from JSON objects */ -export const BackupSchema: t.Type = t.intersection([ - // Required fields - t.type({ - encryptedData: BufferFromBase64, - nonce: BufferFromBase64, - version: t.string, - }), - // Optional fields - // https://github.com/gcanti/io-ts/blob/master/index.md#mixing-required-and-optional-props - t.partial({ - odisDomain: SequentialDelayDomainSchema, - metadata: t.UnknownRecord, - encryptedFuseKey: BufferFromBase64, - computationalHardening: t.union([ - t.type({ - function: t.literal(ComputationalHardeningFunction.PBKDF), - iterations: t.number, - }), - t.intersection([ - t.type({ - function: t.literal(ComputationalHardeningFunction.SCRYPT), - cost: t.number, - }), - t.partial({ - blockSize: t.number, - parallelization: t.number, - }), - ]), - ]), - environment: t.partial({ - odis: t.type({ - odisUrl: t.string, - odisPubKey: t.string, - }), - circuitBreaker: t.type({ - url: t.string, - publicKey: t.string, - }), - }), - }), -]) - -export function serializeBackup(backup: Backup): string { - return JSON.stringify(BackupSchema.encode(backup)) -} - -export function deserializeBackup(data: string): Result { - const jsonDecode = parseJsonAsResult(data) - if (!jsonDecode.ok) { - return Err(new DecodeError(jsonDecode.error)) - } - - const decoding = BackupSchema.decode(jsonDecode.result) - if (isLeft(decoding)) { - return Err( - new DecodeError( - new Error(`error in validating backup object: ${JSON.stringify(decoding.left)}`) - ) - ) - } - const backup = decoding.right - - if (backup.nonce.length !== 32) { - return Err( - new DecodeError( - new Error(`expected backup nonce to be 32 bytes but got ${backup.nonce.length}`) - ) - ) - } - - return Ok(backup) -} diff --git a/packages/sdk/encrypted-backup/src/utils.ts b/packages/sdk/encrypted-backup/src/utils.ts deleted file mode 100644 index 0637ec4e85..0000000000 --- a/packages/sdk/encrypted-backup/src/utils.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { Err, Ok, Result } from '@celo/base/lib/result' -import { ReadOnlyWallet } from '@celo/connect' -import * as crypto from 'crypto' -import { ComputationalHardeningConfig, ComputationalHardeningFunction } from './config' -import { DecryptionError, EncryptionError, PbkdfError, ScryptError } from './errors' - -// NOTE: This module is intended for use within the @celo/encrypted-backup package and so is not -// exported in the index.ts file. - -/** Pared down ReadOnlyWallet type that supports the required functions of EIP-712 signing. */ -export type EIP712Wallet = Pick - -/** Info strings to separate distinct usages of the key derivation function */ -export enum KDFInfo { - PASSWORD = 'Celo Backup Password and Nonce', - FUSE_KEY = 'Celo Backup Fuse Key', - ODIS_AUTH_KEY = 'Celo Backup ODIS Request Authorization Key', - ODIS_KEY_HARDENING = 'Celo Backup ODIS Key Hardening', - PBKDF = 'Celo Backup PBKDF Hardening', - SCRYPT = 'Celo Backup scrypt Hardening', - FINALIZE = 'Celo Backup Key Finalization', -} - -/** - * Key derivation function for mixing source keying material. - * - * @remarks This function does not add any hardening to the input keying material. It is used only - * to mix the provided key material sources. It's output should not be used to directly derive a key - * from a password or other low entropy sources. - * - * @param info Fixed string value used for domain separation. - * @param sources An array of keying material source values (e.g. a password and a nonce). - */ -export function deriveKey(info: KDFInfo, sources: Buffer[]): Buffer { - // Hash each source keying material component, and the info value, to prevent hashing collisions - // that might result if the variable length data is simply concatenated. - const chunks = [Buffer.from(info, 'utf8'), ...sources].map((source: Buffer) => { - const hash = crypto.createHash('sha256') - hash.update(source) - return hash.digest() - }) - - // NOTE: We would prefer to use HKDF here, but is only available in Node v15 and above. - return crypto.pbkdf2Sync(Buffer.concat(chunks), Buffer.alloc(0), 1, 32, 'sha256') -} - -/** - * AES-256-GCM encrypt the given data with the given 32-byte key. - * Encode the ciphertext as { iv || data || auth tag } - */ -export function encrypt(key: Buffer, data: Buffer): Result { - try { - // NOTE: AES-GCM uses a 12-byte nonce. Longer nonces get hashed before use. - const nonce = crypto.randomBytes(12) - const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce) - return Ok(Buffer.concat([nonce, cipher.update(data), cipher.final(), cipher.getAuthTag()])) - } catch (error) { - return Err(new EncryptionError(error as Error)) - } -} - -/** - * AES-256-GCM decrypt the given data with the given 32-byte key. - * Ciphertext should be encoded as { iv || data || auth tag }. - */ -export function decrypt(key: Buffer, ciphertext: Buffer): Result { - const len = ciphertext.length - if (len < 28) { - return Err( - new DecryptionError( - new Error(`ciphertext is too short: expected at least 28 bytes, but got ${len}`) - ) - ) - } - - try { - // NOTE: AES-GCM uses a 12-byte nonce. Longer nonces get hashed before use. - const nonce = ciphertext.slice(0, 12) - const ciphertextData = ciphertext.slice(12, len - 16) - const auth = ciphertext.slice(len - 16, len) - const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce) - decipher.setAuthTag(auth) - return Ok(Buffer.concat([decipher.update(ciphertextData), decipher.final()])) - } catch (error) { - return Err(new DecryptionError(error as Error)) - } -} - -/** - * PBKDF2-SHA256 computational key hardening. - * - * @remarks When possible, a memory hard function such as scrypt should be used instead. - * No salt parameter is provided as the intended use case of this function is to harden a - * key value which is derived from a password but already has the salt mixed in. - * - * @see { @link - * https://nodejs.org/api/crypto.html#cryptopbkdf2password-salt-iterations-keylen-digest-callback | - * NodeJS crypto.pbkdf2 API } - * - * @param key Key buffer to compute hardening against. Should have a salt or nonce mixed in. - * @param iterations Number of PBKDF2 iterations to execute for key hardening. - */ -export function pbkdf2(key: Buffer, iterations: number): Promise> { - return new Promise((resolve) => { - crypto.pbkdf2(key, KDFInfo.PBKDF, iterations, 32, 'sha256', (error, result) => { - if (error) { - resolve(Err(new PbkdfError(iterations, error))) - } - resolve(Ok(result)) - }) - }) -} - -/** Cost parameters for the scrypt computational hardening function. */ -export interface ScryptOptions { - cost: number - blockSize?: number - parallelization?: number -} - -/** - * scrypt computational key hardening. - * - * @remarks No salt parameter is provided as the intended use case of this function is to harden a - * key value which is derived from a password but already has the salt mixed in. - * - * @see { @link - * https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback | - * NodeJS crypto.scrypt API } - * - * @param key Key buffer to compute hardening against. Should have a salt or nonce mixed in. - * @param options Options to control the cost of the scrypt function. - */ -export function scrypt(key: Buffer, options: ScryptOptions): Promise> { - // Define the maxmem parameter to be large enough to accommodate the provided options. - // See the Node JS crypto implementation of scrypt for more detail. - const maxmem = Math.max(32 * 1024 * 1024, 128 * options.cost * (options.blockSize ?? 8)) - return new Promise((resolve) => { - crypto.scrypt(key, KDFInfo.SCRYPT, 32, { maxmem, ...options }, (error, result) => { - if (error) { - resolve(Err(new ScryptError(options, error))) - } - resolve(Ok(result)) - }) - }) -} - -export function computationalHardenKey( - key: Buffer, - config: ComputationalHardeningConfig -): Promise> { - switch (config.function) { - case ComputationalHardeningFunction.PBKDF: - return pbkdf2(key, config.iterations) - case ComputationalHardeningFunction.SCRYPT: - return scrypt(key, config) - } -} diff --git a/packages/sdk/encrypted-backup/tsconfig.json b/packages/sdk/encrypted-backup/tsconfig.json deleted file mode 100644 index 25d848de42..0000000000 --- a/packages/sdk/encrypted-backup/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "@celo/typescript/tsconfig.library.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "lib" - }, - "include": ["src/**/*", "types/**/*"] -} diff --git a/packages/sdk/encrypted-backup/tslint.json b/packages/sdk/encrypted-backup/tslint.json deleted file mode 100644 index 036f000683..0000000000 --- a/packages/sdk/encrypted-backup/tslint.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": ["@celo/typescript/tslint.json"], - "rules": { - "no-global-arrow-functions": false, - "no-console": false, - "member-ordering": false, - "max-classes-per-file": false - } -} diff --git a/packages/sdk/encrypted-backup/typedoc.json b/packages/sdk/encrypted-backup/typedoc.json deleted file mode 100644 index 6947ff8252..0000000000 --- a/packages/sdk/encrypted-backup/typedoc.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "mode": "modules", - "exclude": ["**/generated/*.ts", "**/*+(index|.test).ts"], - "excludeNotExported": true, - "excludePrivate": true, - "excludeProtected": true, - "includeDeclarations": false, - "ignoreCompilerErrors": true, - "hideGenerator": "true", - "out": "../../docs/sdk/docs/encrypted-backup", - "gitRevision": "master", - "readme": "none" - } \ No newline at end of file diff --git a/packages/sdk/identity/.gitignore b/packages/sdk/identity/.gitignore deleted file mode 100644 index 7fabe89f61..0000000000 --- a/packages/sdk/identity/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -lib/ -tmp/ -.tmp/ -.env \ No newline at end of file diff --git a/packages/sdk/identity/.npmignore b/packages/sdk/identity/.npmignore deleted file mode 100644 index 026e06c0cd..0000000000 --- a/packages/sdk/identity/.npmignore +++ /dev/null @@ -1,23 +0,0 @@ -/.devchain/ -/.devchain.tar.gz -/coverage/ -/node_modules/ -/src/ -/tmp/ -/.tmp/ - -/tslint.json -/tsconfig.* -/jest.config.* -*.tgz - -/src - -/lib/**/*.test.* -/lib/test-utils -# exclude ts files and sourcemaps -*.map -*.ts - -# include the .d.ts files -!lib/**/*.d.ts \ No newline at end of file diff --git a/packages/sdk/identity/README.md b/packages/sdk/identity/README.md deleted file mode 100644 index 2fa8d9b209..0000000000 --- a/packages/sdk/identity/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# @celo/identity - -@celo/identity simplifies interacting with ODIS, Celo’s lightweight identity layer based on phone numbers. - -You can find an example of how to use this package to do ODIS lookups [here](https://github.com/critesjosh/register-number). diff --git a/packages/sdk/identity/jest.config.js b/packages/sdk/identity/jest.config.js deleted file mode 100644 index eda2fca0d1..0000000000 --- a/packages/sdk/identity/jest.config.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - preset: 'ts-jest', - rootDir: './src/', - testMatch: ['/**/?(*.)+(spec|test).ts?(x)'], - setupFilesAfterEnv: ['@celo/dev-utils/lib/matchers'], - globalSetup: '/test-utils/setup.global.ts', - globalTeardown: '/test-utils/teardown.global.ts', - verbose: true, -} diff --git a/packages/sdk/identity/package.json b/packages/sdk/identity/package.json deleted file mode 100644 index bcf8b5bafd..0000000000 --- a/packages/sdk/identity/package.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "name": "@celo/identity", - "version": "5.0.4", - "description": "Utilities for interacting with Celo's identity protocol", - "main": "./lib/index.js", - "types": "./lib/index.d.ts", - "author": "Celo", - "license": "Apache-2.0", - "homepage": "https://celo-sdk-docs.readthedocs.io/en/latest/identity", - "repository": "https://github.com/celo-org/celo-monorepo/tree/master/packages/sdk/identity", - "keywords": [ - "celo", - "blockchain", - "contractkit", - "odis" - ], - "scripts": { - "build": "tsc -b .", - "clean": "tsc -b . --clean", - "docs": "typedoc", - "test:reset": "yarn --cwd ../../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../../dev-utils/src/migration-override.json --upto 27", - "test:livechain": "yarn --cwd ../../protocol devchain run-tar .tmp/devchain.tar.gz", - "test": "jest --runInBand --testPathIgnorePatterns src/odis/identifier-backwards-compatibility.test.ts", - "lint": "tslint -c tslint.json --project .", - "prepublishOnly": "yarn build" - }, - "dependencies": { - "@celo/base": "5.0.4", - "@celo/utils": "5.0.4", - "@celo/contractkit": "5.0.4", - "@celo/phone-number-privacy-common": "^3.0.3", - "@types/debug": "^4.1.5", - "bignumber.js": "^9.0.0", - "blind-threshold-bls": "npm:@celo/blind-threshold-bls@1.0.0-beta", - "cross-fetch": "3.0.6", - "debug": "^4.1.1", - "elliptic": "^6.5.4", - "ethereum-cryptography": "1.2.0", - "fp-ts": "2.1.1", - "io-ts": "2.0.1" - }, - "devDependencies": { - "@celo/dev-utils": "0.0.1", - "@celo/wallet-local": "5.0.4", - "@types/elliptic": "^6.4.12", - "fetch-mock": "9.10.4", - "ganache": "npm:@celo/ganache@7.8.0-unofficial.0", - "old-identity-sdk": "npm:@celo/identity@1.5.2" - }, - "engines": { - "node": ">=12.9.0" - } -} \ No newline at end of file diff --git a/packages/sdk/identity/src/__mocks__/cross-fetch.ts b/packages/sdk/identity/src/__mocks__/cross-fetch.ts deleted file mode 100644 index 7435c07d5e..0000000000 --- a/packages/sdk/identity/src/__mocks__/cross-fetch.ts +++ /dev/null @@ -1,3 +0,0 @@ -const fetchMockSandbox = require('fetch-mock').sandbox() - -export default fetchMockSandbox diff --git a/packages/sdk/identity/src/index.ts b/packages/sdk/identity/src/index.ts deleted file mode 100644 index 4f398dd464..0000000000 --- a/packages/sdk/identity/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { OdisUtils } from './odis' diff --git a/packages/sdk/identity/src/odis/bls-blinding-client.ts b/packages/sdk/identity/src/odis/bls-blinding-client.ts deleted file mode 100644 index 9ceedf633c..0000000000 --- a/packages/sdk/identity/src/odis/bls-blinding-client.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { randomBytes } from 'crypto' - -export interface BlsBlindingClient { - blindMessage: (base64PhoneNumber: string, seed?: Buffer) => Promise - unblindAndVerifyMessage: (blindedMessage: string) => Promise -} - -// The following interfaces should match https://github.com/celo-org/blind-threshold-bls-wasm/blob/master/src/blind_threshold_bls.d.ts - -interface ThresholdBlsLib { - blind: (message: Uint8Array, seed: Uint8Array) => BlindedMessage - unblind: (blindedSignature: Uint8Array, blindingFactor: Uint8Array) => Uint8Array - verify: (publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array) => void // throws on failure -} - -interface BlindedMessage { - blindingFactor: Uint8Array - message: Uint8Array -} - -export class WasmBlsBlindingClient implements BlsBlindingClient { - private thresholdBls: ThresholdBlsLib - private odisPubKey: Uint8Array - private blindedValue: BlindedMessage | undefined - private rawMessage: Buffer | undefined - - constructor(odisPubKey: string) { - this.odisPubKey = Buffer.from(odisPubKey, 'base64') - // Dynamically load the Wasm library - // Checkout out documentation for alternative runtime environments: - // https://github.com/celo-org/identity/tree/ASv2/asv2#runtime-environments - if (this.isReactNativeEnvironment()) { - throw new Error('Cannot use WasmBlsBlindingClient in a React Native app') - } else if (this.isBrowserEnvironment()) { - throw new Error('Cannot use WasmBlsBlindingClient in a browser environment') - } else { - this.thresholdBls = require('blind-threshold-bls') - } - } - - async blindMessage(base64PhoneNumber: string, seed?: Buffer): Promise { - const userSeed = seed ?? randomBytes(32) - if (!seed) { - console.warn( - 'Warning: Use a private deterministic seed (e.g. DEK private key) to preserve user quota when requests are replayed.' - ) - } - this.rawMessage = Buffer.from(base64PhoneNumber, 'base64') - this.blindedValue = await this.thresholdBls.blind(this.rawMessage, userSeed) - const blindedMessage = this.blindedValue.message - return Buffer.from(blindedMessage).toString('base64') - } - - async unblindAndVerifyMessage(base64BlindSig: string): Promise { - if (!this.rawMessage || !this.blindedValue) { - throw new Error('Must call blind before unblinding') - } - - const blindedSignature = Buffer.from(base64BlindSig, 'base64') - const unblindMessage = await this.thresholdBls.unblind( - blindedSignature, - this.blindedValue.blindingFactor - ) - // this throws on error - await this.thresholdBls.verify(this.odisPubKey, this.rawMessage, unblindMessage) - return Buffer.from(unblindMessage).toString('base64') - } - - private isReactNativeEnvironment(): boolean { - return typeof navigator !== 'undefined' && navigator.product === 'ReactNative' - } - - // https://stackoverflow.com/questions/17575790/environment-detection-node-js-or-browser - // tslint:disable-next-line: function-constructor - private isBrowserEnvironment = new Function('try {return this===window;}catch(e){ return false;}') -} diff --git a/packages/sdk/identity/src/odis/circuit-breaker.mock.ts b/packages/sdk/identity/src/odis/circuit-breaker.mock.ts deleted file mode 100644 index cc08051b77..0000000000 --- a/packages/sdk/identity/src/odis/circuit-breaker.mock.ts +++ /dev/null @@ -1,139 +0,0 @@ -import * as crypto from 'crypto' -import debugFactory from 'debug' -import fetchMock from '../__mocks__/cross-fetch' -import { - BASE64_REGEXP, - CircuitBreakerEndpoints, - CircuitBreakerKeyStatus, - CircuitBreakerServiceContext, - CircuitBreakerStatusResponse, - CircuitBreakerUnwrapKeyRequest, - CircuitBreakerUnwrapKeyResponse, -} from './circuit-breaker' - -const debug = debugFactory('kit:identity:odis:circuit-breaker:mock') - -export const MOCK_CIRCUIT_BREAKER_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGtxUPljt+oHFBf3RrDZHN9TbT -iI0kK4bv02Z2WP7kU/PQCikWqNl9/VjGVXuGMlfwpcWZrjWwJa+kBlUYRXH/inXW -UKO5PqTnaUXS1ALasGAUvzRz3VvzCxpjKsjVS8/gAoJbY2Imwor432OLrOssNoK7 -jbl1TgaV47yGCKwF9wIDAQAB ------END PUBLIC KEY-----` - -export const MOCK_CIRCUIT_BREAKER_PRIVATE_KEY = `-----BEGIN PRIVATE KEY----- -MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMa3FQ+WO36gcUF/ -dGsNkc31NtOIjSQrhu/TZnZY/uRT89AKKRao2X39WMZVe4YyV/ClxZmuNbAlr6QG -VRhFcf+KddZQo7k+pOdpRdLUAtqwYBS/NHPdW/MLGmMqyNVLz+ACgltjYibCivjf -Y4us6yw2gruNuXVOBpXjvIYIrAX3AgMBAAECgYBGPqv8QZAweAjxLVv7B+X112wV -JN033wcpOiKrTVR1ZFP4w864iuGvTuKV4dvzmVJK6F7Mr6+c4AWRxwdHuCzOlwxj -O9RySFAXhoENu70zg8W2w4i8GMHsmdnNk045cF01Mb3GtQ6Y3uGb637XYTIwMEbC -Q74TbkrfPZPcSIpPEQJBAP4VModTr47oNvdyJITQ3fzIarRSDU0deZTpn6MXB3a1 -abOAzlqYK3CSvLyyM9GOB9C5wvIZev+aNU9SkqPzU38CQQDINu7nOqS2X8UXQ5sS -wFrnoBQcU78i7Jaopvw0kOvkvklHlKVvXVkWP8PaWYdUAO9fpEdKdRnfaOEnqBwT -aymJAkEAgTXmbEtyjAoracryJ1jQiyyglvLjMMQ8gC4OsLGVahj3mAF47zlTXfxB -XvSAxaCk+NB/Av9SPYn+ckhbqmSjoQJAYb6H1bVIkoyg0OG9hGMKPkhlaQrtpmQw -jTewqw0RTQQlDGAigALnqjgJKsFIkxc9xciS0WPn9KzkNxMYWdaYWQJBAI8asXXb -XF5Lg2AAM2xJ/SS+h+si4f70eZey4vo9pWB3Q+VKbtRZu2pCjlR1A1nIqigJxdlc -1jHX+4GiW+t0w8Q= ------END PRIVATE KEY-----` - -const MOCK_CIRCUIT_BREAKER_ENVIRONMENT: CircuitBreakerServiceContext = { - url: 'https://mockcircuitbreaker.com/', - publicKey: MOCK_CIRCUIT_BREAKER_PUBLIC_KEY, -} - -/** - * Mock circuit breaker implementation based on Valora implementaion - * github.com/valora-inc/wallet/tree/main/packages/cloud-functions/src/circuitBreaker/circuitBreaker.ts - */ -export class MockCircuitBreaker { - static readonly publicKey = MOCK_CIRCUIT_BREAKER_PUBLIC_KEY - static readonly privateKey = MOCK_CIRCUIT_BREAKER_PRIVATE_KEY - static readonly environment = MOCK_CIRCUIT_BREAKER_ENVIRONMENT - - public keyStatus: CircuitBreakerKeyStatus = CircuitBreakerKeyStatus.ENABLED - - status(): { status: number; body: CircuitBreakerStatusResponse } { - return { - status: 200, - body: { status: this.keyStatus }, - } - } - - unwrapKey(req: CircuitBreakerUnwrapKeyRequest): { - status: number - body: CircuitBreakerUnwrapKeyResponse - } { - const { ciphertext } = req - if (!ciphertext) { - return { - status: 400, - body: { error: '"ciphertext" parameter must be provided' }, - } - } else if (!BASE64_REGEXP.test(ciphertext)) { - return { - status: 400, - body: { error: '"ciphertext" parameter must be a base64 encoded buffer' }, - } - } - - if (this.keyStatus !== CircuitBreakerKeyStatus.ENABLED) { - return { - status: 503, - body: { status: this.keyStatus }, - } - } - - let plaintext: Buffer - try { - plaintext = crypto.privateDecrypt( - // @ts-ignore support for OAEP hash option, was added in Node 12.9.0. - { key: MOCK_CIRCUIT_BREAKER_PRIVATE_KEY, oaepHash: 'sha256' }, - Buffer.from(ciphertext, 'base64') - ) - } catch (error) { - return { - status: 500, - body: { error: 'Error while decrypting ciphertext' }, - } - } - - return { - status: 200, - body: { plaintext: plaintext.toString('base64') }, - } - } - - installStatusEndpoint(mock: typeof fetchMock, override?: any) { - mock.mock( - { - url: new URL(CircuitBreakerEndpoints.STATUS, MockCircuitBreaker.environment.url).href, - method: 'GET', - }, - override ?? - ((url: string, req: unknown) => { - debug('Mocking request', { url, req }) - return this.status() - }) - ) - } - - installUnwrapKeyEndpoint(mock: typeof fetchMock, override?: any) { - mock.mock( - { - url: new URL(CircuitBreakerEndpoints.UNWRAP_KEY, MockCircuitBreaker.environment.url).href, - method: 'POST', - }, - override ?? - ((url: string, req: { body: string }) => { - debug('Mocking request', { url, req }) - return this.unwrapKey(JSON.parse(req.body) as CircuitBreakerUnwrapKeyRequest) - }) - ) - } - - install(mock: typeof fetchMock) { - this.installStatusEndpoint(mock) - this.installUnwrapKeyEndpoint(mock) - } -} diff --git a/packages/sdk/identity/src/odis/circuit-breaker.test.ts b/packages/sdk/identity/src/odis/circuit-breaker.test.ts deleted file mode 100644 index 11ef00efdb..0000000000 --- a/packages/sdk/identity/src/odis/circuit-breaker.test.ts +++ /dev/null @@ -1,178 +0,0 @@ -import * as crypto from 'crypto' -import { - CircuitBreakerClient, - CircuitBreakerErrorTypes, - CircuitBreakerKeyStatus, -} from './circuit-breaker' -import { MockCircuitBreaker } from './circuit-breaker.mock' -import fetchMock from '../__mocks__/cross-fetch' - -describe('CircuitBreakerClient', () => { - const client = new CircuitBreakerClient(MockCircuitBreaker.environment) - let mockService: MockCircuitBreaker | undefined - - beforeEach(() => { - fetchMock.reset() - fetchMock.config.overwriteRoutes = true - - // Mock the circuit breaker service using the mock implementation defined above. - mockService = new MockCircuitBreaker() - mockService.install(fetchMock) - }) - - afterEach(() => { - fetchMock.reset() - }) - - describe('.status()', () => { - it('should fetch the current circuit breaker status', async () => { - for (const status of Object.values(CircuitBreakerKeyStatus)) { - mockService!.keyStatus = status - const result = await client.status() - expect(result.ok).toBe(true) - if (!result.ok) { - continue - } - expect(result.result).toEqual(status) - } - }) - - it('should return an error if fetch throws', async () => { - mockService!.installStatusEndpoint(fetchMock, { throws: new Error('fetch error') }) - const result = await client.status() - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.FETCH_ERROR) - }) - - it('should return an error if the fetch returns an HTTP error status', async () => { - mockService!.installStatusEndpoint(fetchMock, { status: 501 }) - const result = await client.status() - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.SERVICE_ERROR) - }) - - it('should return an error if fetch results in invalid json', async () => { - mockService!.installStatusEndpoint(fetchMock, { status: 200, body: '' }) - const result = await client.status() - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.SERVICE_ERROR) - }) - - it('should return an error if fetch results in an invalid status', async () => { - mockService!.installStatusEndpoint(fetchMock, { - status: 200, - body: { status: 'invalid status' }, - }) - const result = await client.status() - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.SERVICE_ERROR) - }) - }) - - describe('.wrapKey()', () => { - it('should return an encryption of the given plaintext', async () => { - const testData = 'test circuit breaker plaintext' - const ciphertext = client.wrapKey(Buffer.from(testData)) - expect(ciphertext.ok).toBe(true) - if (!ciphertext.ok) { - return - } - - const plaintext = crypto.privateDecrypt( - //@ts-ignore support for OAEP hash option, was added in Node 12.9.0. - { key: MockCircuitBreaker.privateKey, oaepHash: 'sha256' }, - ciphertext.result - ) - expect(plaintext.toString('utf8')).toEqual(testData) - }) - }) - - describe('.unwrapKey()', () => { - const testData = 'test circuit breaker plaintext' - const wrapResult = client.wrapKey(Buffer.from(testData)) - if (!wrapResult.ok) { - throw new Error('failed to produce test ciphertext for unwrapKey') - } - const testCiphertext = wrapResult.result - - it('should decrypt the given ciphertext when the service is enabled', async () => { - const result = await client.unwrapKey(testCiphertext) - expect(result.ok).toBe(true) - if (!result.ok) { - return - } - expect(result.result.toString('utf8')).toEqual(testData) - }) - - it('should return an error status response when the service is disabled', async () => { - for (const status of Object.values(CircuitBreakerKeyStatus)) { - if (status === CircuitBreakerKeyStatus.ENABLED) { - continue - } - - mockService!.keyStatus = status - const result = await client.unwrapKey(testCiphertext) - expect(result.ok).toBe(false) - if (result.ok) { - continue - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.UNAVAILABLE_ERROR) - } - }) - - it('should return an error if fetch throws', async () => { - mockService!.installUnwrapKeyEndpoint(fetchMock, { throws: new Error('fetch error') }) - const result = await client.unwrapKey(testCiphertext) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.FETCH_ERROR) - }) - - it('should return an error if the fetch returns an HTTP error status', async () => { - mockService!.installUnwrapKeyEndpoint(fetchMock, { status: 501 }) - const result = await client.unwrapKey(testCiphertext) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.SERVICE_ERROR) - }) - - it('should return an error if fetch results in invalid json', async () => { - mockService!.installUnwrapKeyEndpoint(fetchMock, { status: 200, body: '' }) - const result = await client.unwrapKey(testCiphertext) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.SERVICE_ERROR) - }) - - it('should return an error if fetch results in an invalid plaintext', async () => { - mockService!.installUnwrapKeyEndpoint(fetchMock, { - status: 200, - body: { plaintext: '' }, - }) - const result = await client.unwrapKey(testCiphertext) - expect(result.ok).toBe(false) - if (result.ok) { - return - } - expect(result.error.errorType).toEqual(CircuitBreakerErrorTypes.SERVICE_ERROR) - }) - }) -}) diff --git a/packages/sdk/identity/src/odis/circuit-breaker.ts b/packages/sdk/identity/src/odis/circuit-breaker.ts deleted file mode 100644 index 58084724db..0000000000 --- a/packages/sdk/identity/src/odis/circuit-breaker.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { Err, Ok, Result, RootError } from '@celo/base/lib/result' -import fetch from 'cross-fetch' -import * as crypto from 'crypto' - -export const BASE64_REGEXP = /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/ - -export interface CircuitBreakerServiceContext { - url: string - publicKey: string -} - -export const VALORA_ALFAJORES_CIRCUIT_BREAKER_ENVIRONMENT: CircuitBreakerServiceContext = { - url: 'https://us-central1-celo-mobile-alfajores.cloudfunctions.net/circuitBreaker/', - publicKey: `-----BEGIN PUBLIC KEY----- -MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsYkNg3iY1ha4KGCGvHLl -mOMKV63lq+WsHIgUGfEuyfOWEBetVux9gvQEEPYpKbHgVQrfcegp28LoZYehWZHC -dIHSACcW0SGZagSOFEgxVSY6MgZZjmbTdlUtLac2cvxIDx8qhkoBjWRWu4g5LfdW -9QA0tiM3dR/pmA8YWcIYtyjGY1zglA/YqHClKsDRY+dbhshfILfohdFsVNJ3CWLS -J4yGvVe78AE/WiaXISV5ol+bqve4QlxzbBLIV4s44YONCh18/YhmGHCuSn8yy1/0 -q3YW7COaFEGd7m8VnV2rU/dFLKyF0XEanS6xk9ciL9uafR9dMryEQ7AW+yKmfQBG -H2i5uiKnWW2a3a873ShG2Qphl9mw1Kcrdxug4qk9y7RoKlMnG3Wdr4HMQb9S8KYf -07ZyVEbFip26ANWGo8dCA8fWvVtU5DByoWPI+PuglOB22z2noXov98imSFJfz9vu -yGAQt3CUOwUQvt+RObDXiHHIxJjU+6/81X3Jdnt3dFEfAgMBAAE= ------END PUBLIC KEY-----`, -} - -export const VALORA_MAINNET_CIRCUIT_BREAKER_ENVIRONMENT: CircuitBreakerServiceContext = { - url: 'https://us-central1-celo-mobile-mainnet.cloudfunctions.net/circuitBreaker/', - publicKey: `-----BEGIN PUBLIC KEY----- -MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEArQ89m/HIGECXR7ceZZRS -b6MZEw1S1o5qgi6sLEejBMUQhM/wgySoo5ydiW7S4iyiqEksQNAlOs5Mrv1aE9Ul -bG+rpglOA1xYLyjY7xUZE2tyPksPXcSKgu6d+G9gVtbmFld1Kr0jVx4qOLejtH3S -dGbX6g9GshgB1W4iEDZ4qEJBuvItSTudK3BFM1mBfEq1w3kDxNzYKC1zFlw+DWWh -BgIPB7zEp+MJNTwel2z7H02wsEMJMXzKwaAWaDp8PYfF3RwgCDIFkf+QteYIEUrG -C9bFhdYpDGY9Ldiz7kca9G9dvXWpZUQOYyOY7CFx0k2XcTBwx4Lq524lNR8waIDu -OT5jj2SIwXf5eKtyFMUqRNnqgs+IHHcWgh0CH7mfhPlFBMivKlwHgQqCJH3rHlgu -CMi3ENv4+p7+svshngntxGkEzZcLV3YVW7BG6xSOAqC1tjkM1PkmXENQOq+bxAL6 -bg3W6cTRQAQxoicu6+1c5Tdb/K36TXx0mHan7/Z8JCqfAgMBAAE= ------END PUBLIC KEY-----`, -} - -export enum CircuitBreakerKeyStatus { - ENABLED = 'ENABLED', - DISABLED = 'DISABLED', - DESTROYED = 'DESTROYED', - UNKNOWN = 'UNKNOWN', -} - -export enum CircuitBreakerEndpoints { - HEALTH = 'health', - STATUS = 'status', - UNWRAP_KEY = 'unwrap-key', -} - -export interface CircuitBreakerStatusResponse { - /** Status of the circuit breaker service */ - status: CircuitBreakerKeyStatus -} - -export interface CircuitBreakerUnwrapKeyRequest { - /** RSA-OAEP-256 encrypted data to be unwrapped by the circuit breaker. Encoded as base64 */ - ciphertext: string -} - -export interface CircuitBreakerUnwrapKeyResponse { - /** Decryption of the ciphertext provided to the circuit breaker service */ - plaintext?: string - - /** Error message indicating what went wrong if the ciphertext could not be decrypted */ - error?: string - - /** Status of the circuit breaker service. Included if the service is not enabled. */ - status?: CircuitBreakerKeyStatus -} - -export enum CircuitBreakerErrorTypes { - FETCH_ERROR = 'FETCH_ERROR', - SERVICE_ERROR = 'CIRCUIT_BREAKER_SERVICE_ERROR', - UNAVAILABLE_ERROR = 'CIRCUIT_BREAKER_UNAVAILABLE_ERROR', - ENCRYPTION_ERROR = 'ENCRYPTION_ERROR', -} - -export class CircuitBreakerServiceError extends RootError { - constructor(readonly status: number, readonly error?: Error) { - super(CircuitBreakerErrorTypes.SERVICE_ERROR) - } -} - -export class CircuitBreakerUnavailableError extends RootError { - constructor(readonly status: CircuitBreakerKeyStatus) { - super(CircuitBreakerErrorTypes.UNAVAILABLE_ERROR) - } -} - -export class EncryptionError extends RootError { - constructor(readonly error?: Error) { - super(CircuitBreakerErrorTypes.ENCRYPTION_ERROR) - } -} - -export class FetchError extends RootError { - constructor(readonly error?: Error) { - super(CircuitBreakerErrorTypes.FETCH_ERROR) - } -} - -export type CircuitBreakerError = - | CircuitBreakerServiceError - | CircuitBreakerUnavailableError - | EncryptionError - | FetchError - -/** - * Client for interacting with a circuit breaker service for encrypted cloud backups. - * - * @remarks A circuit breaker is a service supporting a public decryption function backed by an HSM - * key. If the need arises, the circuit breaker operator may take the decryption function offline. - * A client can encrypt data to the circuit breaker public key and store it in a non-public place. - * This data will then be available under normal circumstances, but become unavailable in the case - * of an emergency. - * - * It is intended for use in password-based key derivation when ODIS is used as a key hardening - * function. Clients may include in their key dervivation a random value which they encrypt to the - * circuit breaker public key. This allows the circuit breaker operator to disable key derivation, - * by restricting access to the encrypted keying material, in the event that ODIS is conpromised. - * This acts as a safety measure to allow wallet providers, or other users of ODIS key hardening, to - * prevent attackers from being able to brute force their users' derived keys in the event that - * ODIS is compromised such that it can no longer add to the key hardening. - * - * The circuit breaker service is designed for use in the encrypted cloud backup protocol. More - * information about encrypted cloud backup and the circuit breaker service can be found in the - * official {@link https://docs.celo.org/celo-codebase/protocol/identity/encrypted-cloud-backup | - * Celo documentation} - */ -export class CircuitBreakerClient { - constructor(readonly environment: CircuitBreakerServiceContext) {} - - protected url(endpoint: CircuitBreakerEndpoints): string { - // Note that if the result of this is an invalid URL, the URL constructor will throw. This is - // caught and reported as a fetch error, as a request could not be made. - return new URL(endpoint, this.environment.url).href - } - - /** - * Check the current status of the circuit breaker service. Result will reflect whether or not - * the circuit breaker keys are currently available. - */ - async status(): Promise> { - let response: Response - try { - response = await fetch(this.url(CircuitBreakerEndpoints.STATUS), { - method: 'GET', - headers: { - Accept: 'application/json', - }, - }) - } catch (error) { - return Err(new FetchError(error as Error)) - } - - let obj: any - try { - obj = await response.json() - } catch (error) { - return Err(new CircuitBreakerServiceError(response.status, error as Error)) - } - - // If the response was an error code, return an error to the user. - // We do not expect an error message to be included with the response from the status endpoint. - if (!response.ok) { - return Err(new CircuitBreakerServiceError(response.status)) - } - - if (!Object.values(CircuitBreakerKeyStatus).includes(obj.status)) { - return Err( - new CircuitBreakerServiceError( - response.status, - new Error(`circuit breaker service returned unexpected response: ${obj.status}`) - ) - ) - } - - return Ok(obj.status as CircuitBreakerKeyStatus) - } - - /** - * RSA-OAEP-256 Encrypt the provided key value against the public key of the circuit breaker. - * - * @remarks Note that this is an entirely local procedure and does not require interaction with - * the circuit breaker service. Encryption occurs only against the service public key. - */ - wrapKey(plaintext: Buffer): Result { - let ciphertext: Buffer - try { - ciphertext = crypto.publicEncrypt( - { - key: this.environment.publicKey, - oaepHash: 'sha256', - }, - plaintext - ) - } catch (error) { - return Err(new EncryptionError(error as Error)) - } - return Ok(ciphertext) - } - - /** Request the circuit breaker service to decrypt the provided encrypted key value */ - async unwrapKey(ciphertext: Buffer): Promise> { - const request: CircuitBreakerUnwrapKeyRequest = { - ciphertext: ciphertext.toString('base64'), - } - - let response: Response - try { - response = await fetch(this.url(CircuitBreakerEndpoints.UNWRAP_KEY), { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(request), - }) - } catch (error) { - return Err(new FetchError(error as Error)) - } - - let obj: any - try { - obj = await response.json() - } catch (error) { - return Err(new CircuitBreakerServiceError(response.status, error as Error)) - } - - // If the response was an error code, return an error to the user after trying to parse the - // error from the service response. Either an error message or a status value may be returned. - if (!response.ok) { - if (obj.error !== undefined || obj.status === undefined) { - return Err(new CircuitBreakerServiceError(response.status, obj.error)) - } else { - return Err(new CircuitBreakerUnavailableError(obj.status)) - } - } - - const plaintext = obj.plaintext - if (plaintext === undefined || !BASE64_REGEXP.test(plaintext)) { - // Plaintext value is not returned in the error as it may has sensitive information. - const error = new Error('circuit breaker returned invalid plaintext response') - return Err(new CircuitBreakerServiceError(response.status, error)) - } - - return Ok(Buffer.from(plaintext, 'base64')) - } -} diff --git a/packages/sdk/identity/src/odis/identifier-backwards-compatibility.test.ts b/packages/sdk/identity/src/odis/identifier-backwards-compatibility.test.ts deleted file mode 100644 index 5e7d4f455e..0000000000 --- a/packages/sdk/identity/src/odis/identifier-backwards-compatibility.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { getPhoneHash } from '@celo/base' -import { soliditySha3 } from '@celo/utils/lib/solidity' -import { OdisUtils as OdisUtilsOld } from 'old-identity-sdk' -import { OdisUtils } from '../../lib' -import { WasmBlsBlindingClient } from './bls-blinding-client' -import { AuthenticationMethod, AuthSigner, getServiceContext, OdisContextName } from './query' -import fetchMock from '../__mocks__/cross-fetch' - -const { getBlindedIdentifier, getIdentifierHash, getObfuscatedIdentifier, IdentifierPrefix } = - OdisUtils.Identifier - -const mockE164Number = '+14155550000' -const mockAccount = '0x755dB5fF7B82e9a96e0dDDD143293dc2ADeC0050' -// const mockPrivateKey = '0x2cacaf965ae80da49d5b1fc4b4c9b08ffc35971a584aedcc1cb8322b9d5fd9c9' - -// this DEK has been registered to the above account on alfajores -const dekPrivateKey = '0xc2bbdabb440141efed205497a41d5fb6114e0435fd541e368dc628a8e086bfee' - -const authSigner: AuthSigner = { - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - rawKey: dekPrivateKey, -} -const oldServiceContext = OdisUtilsOld.Query.getServiceContext('alfajores') -const currentServiceContext = getServiceContext(OdisContextName.ALFAJORES) - -const expectedObfuscatedIdentifier = - '0xf82c6272fd57d3e5d4e291be16b3ebac5c616084a5e6f3730c73f62efd39c6ae' -const expectedPepper = 'Pi4Z1NQnfsdvJ' - -describe('backwards compatibility of phone number identifiers', () => { - beforeAll(() => { - fetchMock.reset() - // disables the mock, lets all calls fall through to the actual network - fetchMock.spy() - }) - - it('should match when using EncryptionSigner', async () => { - const oldRes = await OdisUtilsOld.PhoneNumberIdentifier.getPhoneNumberIdentifier( - mockE164Number, - mockAccount, - authSigner, - oldServiceContext - ) - - const currRes = await getObfuscatedIdentifier( - mockE164Number, - IdentifierPrefix.PHONE_NUMBER, - mockAccount, - authSigner, - currentServiceContext - ) - - expect(oldRes.e164Number).toEqual(currRes.plaintextIdentifier) - expect(oldRes.phoneHash).toEqual(expectedObfuscatedIdentifier) - expect(currRes.obfuscatedIdentifier).toEqual(expectedObfuscatedIdentifier) - expect(oldRes.pepper).toEqual(expectedPepper) - expect(currRes.pepper).toEqual(expectedPepper) - }, 20000) - - it('blinded identifier should match', async () => { - const blsBlindingClient = new WasmBlsBlindingClient('') - const seed = Buffer.from( - '44714c0a2b2bacec757a67822a4fbbdfe043cca8c6ae936545ef992f246df1a9', - 'hex' - ) - const oldRes = await OdisUtilsOld.PhoneNumberIdentifier.getBlindedPhoneNumber( - mockE164Number, - blsBlindingClient, - seed - ) - const currentRes = await getBlindedIdentifier( - mockE164Number, - IdentifierPrefix.PHONE_NUMBER, - blsBlindingClient, - seed - ) - - const expectedBlindedIdentifier = - 'fuN6SmbxkYBqVbKZu4SizdyDjavNLK/XguIlwsWUhsWA0hQDoZtsZjQCbXqTnUiA' - - expect(oldRes).toEqual(expectedBlindedIdentifier) - expect(currentRes).toEqual(expectedBlindedIdentifier) - }) - - it('obfuscated identifier should match', async () => { - const sha3 = (v: string) => soliditySha3({ type: 'string', value: v }) - const oldRes = getPhoneHash(sha3, mockE164Number, expectedPepper) - - const currRes = getIdentifierHash(mockE164Number, IdentifierPrefix.PHONE_NUMBER, expectedPepper) - - expect(oldRes).toEqual(expectedObfuscatedIdentifier) - expect(currRes).toEqual(expectedObfuscatedIdentifier) - }) - - it('should not match when different prefix used', async () => { - const oldRes = await OdisUtilsOld.PhoneNumberIdentifier.getPhoneNumberIdentifier( - mockE164Number, - mockAccount, - authSigner, - oldServiceContext - ) - - const currRes = await getObfuscatedIdentifier( - mockE164Number, - '' as typeof IdentifierPrefix.PHONE_NUMBER, - mockAccount, - authSigner, - currentServiceContext - ) - - expect(oldRes.e164Number).toEqual(currRes.plaintextIdentifier) - expect(oldRes.phoneHash).not.toEqual(currRes.obfuscatedIdentifier) - expect(oldRes.pepper).not.toEqual(currRes.pepper) - }) -}) diff --git a/packages/sdk/identity/src/odis/identifier.test.ts b/packages/sdk/identity/src/odis/identifier.test.ts deleted file mode 100644 index 16ea0ce657..0000000000 --- a/packages/sdk/identity/src/odis/identifier.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { CombinerEndpoint } from '@celo/phone-number-privacy-common' -import { WasmBlsBlindingClient } from './bls-blinding-client' -import { - getBlindedIdentifier, - getBlindedIdentifierSignature, - getObfuscatedIdentifier, - getObfuscatedIdentifierFromSignature, - getPepperFromThresholdSignature, - IdentifierPrefix, -} from './identifier' -import { AuthenticationMethod, EncryptionKeySigner, ErrorMessages, ServiceContext } from './query' -import fetchMock from '../__mocks__/cross-fetch' - -jest.mock('./bls-blinding-client', () => { - // tslint:disable-next-line:no-shadowed-variable - class WasmBlsBlindingClient { - blindMessage = (m: string) => m - unblindAndVerifyMessage = (m: string) => m - } - return { - WasmBlsBlindingClient, - } -}) - -const mockOffchainIdentifier = 'twitterHandle' -const mockAccount = '0x0000000000000000000000000000000000007E57' -const expectedIdentifierHash = '0x8d1f580d4e49568883df9092285c0f8336e50d592b944607a613aff804e0b48f' -const expectedPepper = 'nHIvMC9B4j2+H' - -const serviceContext: ServiceContext = { - odisUrl: 'https://mockodis.com', - odisPubKey: - '7FsWGsFnmVvRfMDpzz95Np76wf/1sPaK0Og9yiB+P8QbjiC8FV67NBans9hzZEkBaQMhiapzgMR6CkZIZPvgwQboAxl65JWRZecGe5V3XO4sdKeNemdAZ2TzQuWkuZoA', -} -const endpoint = serviceContext.odisUrl + CombinerEndpoint.PNP_SIGN -const rawKey = '41e8e8593108eeedcbded883b8af34d2f028710355c57f4c10a056b72486aa04' - -const authSigner: EncryptionKeySigner = { - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - rawKey, -} - -describe(getObfuscatedIdentifier, () => { - afterEach(() => { - fetchMock.reset() - }) - - describe('Retrieves a pepper correctly', () => { - it('Using EncryptionKeySigner', async () => { - fetchMock.mock(endpoint, { - success: true, - signature: '0Uj+qoAu7ASMVvm6hvcUGx2eO/cmNdyEgGn0mSoZH8/dujrC1++SZ1N6IP6v2I8A', - performedQueryCount: 5, - totalQuota: 10, - version: '', - }) - - const blsBlindingClient = new WasmBlsBlindingClient(serviceContext.odisPubKey) - const base64BlindedMessage = await getBlindedIdentifier( - mockOffchainIdentifier, - IdentifierPrefix.TWITTER, - blsBlindingClient - ) - const base64BlindSig = await getBlindedIdentifierSignature( - mockAccount, - authSigner, - serviceContext, - base64BlindedMessage - ) - const base64UnblindedSig = await blsBlindingClient.unblindAndVerifyMessage(base64BlindSig) - - await expect( - getObfuscatedIdentifier( - mockOffchainIdentifier, - IdentifierPrefix.TWITTER, - mockAccount, - authSigner, - serviceContext - ) - ).resolves.toMatchObject({ - plaintextIdentifier: mockOffchainIdentifier, - pepper: expectedPepper, - obfuscatedIdentifier: expectedIdentifierHash, - unblindedSignature: base64UnblindedSig, - }) - }) - - it('Preblinding the off-chain identifier', async () => { - fetchMock.mock(endpoint, { - success: true, - signature: '0Uj+qoAu7ASMVvm6hvcUGx2eO/cmNdyEgGn0mSoZH8/dujrC1++SZ1N6IP6v2I8A', - performedQueryCount: 5, - totalQuota: 10, - version: '', - }) - - const blsBlindingClient = new WasmBlsBlindingClient(serviceContext.odisPubKey) - const base64BlindedMessage = await getBlindedIdentifier( - mockOffchainIdentifier, - IdentifierPrefix.TWITTER, - blsBlindingClient - ) - - const base64BlindSig = await getBlindedIdentifierSignature( - mockAccount, - authSigner, - serviceContext, - base64BlindedMessage - ) - - const obfuscatedIdentifierDetails = await getObfuscatedIdentifierFromSignature( - mockOffchainIdentifier, - IdentifierPrefix.TWITTER, - base64BlindSig, - blsBlindingClient - ) - - expect(obfuscatedIdentifierDetails.obfuscatedIdentifier).toEqual(expectedIdentifierHash) - expect(obfuscatedIdentifierDetails.pepper).toEqual(expectedPepper) - }) - }) - - it('Throws quota error', async () => { - fetchMock.mock(endpoint, 403) - - await expect( - getObfuscatedIdentifier( - mockOffchainIdentifier, - IdentifierPrefix.TWITTER, - mockAccount, - authSigner, - serviceContext - ) - ).rejects.toThrow(ErrorMessages.ODIS_QUOTA_ERROR) - }) - - it('Throws auth error', async () => { - fetchMock.mock(endpoint, 401) - await expect( - getObfuscatedIdentifier( - mockOffchainIdentifier, - IdentifierPrefix.TWITTER, - mockAccount, - authSigner, - serviceContext - ) - ).rejects.toThrow(ErrorMessages.ODIS_AUTH_ERROR) - }) -}) - -describe(getPepperFromThresholdSignature, () => { - it('Hashes sigs correctly', () => { - const base64Sig = 'vJeFZJ3MY5KlpI9+kIIozKkZSR4cMymLPh2GHZUatWIiiLILyOcTiw2uqK/LBReA' - const signature = Buffer.from(base64Sig, 'base64') - expect(getPepperFromThresholdSignature(signature)).toBe('piWqRHHYWtfg9') - }) -}) diff --git a/packages/sdk/identity/src/odis/identifier.ts b/packages/sdk/identity/src/odis/identifier.ts deleted file mode 100644 index 9f2e8ddb2c..0000000000 --- a/packages/sdk/identity/src/odis/identifier.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { - getIdentifierHash as baseGetIdentifierHash, - getPrefixedIdentifier, - IdentifierPrefix, - isE164Number, -} from '@celo/base' -import { - CombinerEndpointPNP, - KEY_VERSION_HEADER, - SignMessageRequest, - SignMessageResponseSchema, -} from '@celo/phone-number-privacy-common' -import { soliditySha3 } from '@celo/utils/lib/solidity' -import { createHash } from 'crypto' -import debugFactory from 'debug' -import { BlsBlindingClient, WasmBlsBlindingClient } from './bls-blinding-client' -import { - AuthenticationMethod, - AuthSigner, - EncryptionKeySigner, - getOdisPnpRequestAuth, - queryOdis, - ServiceContext, -} from './query' - -const debug = debugFactory('kit:odis:identifier') -const sha3 = (v: string) => soliditySha3({ type: 'string', value: v }) - -const PEPPER_CHAR_LENGTH = 13 - -// Docstring is duplicated in @celo/base; make sure to update in both places. -/** - * Standardized prefixes for ODIS identifiers. - * - * @remarks These prefixes prevent collisions between off-chain identifiers. - * i.e. if a user's instagram and twitter handles are the same, - * these prefixes prevent the ODIS identifers from being the same. - * - * If you would like to use a prefix that isn't included, please put up a PR - * adding it to @celo/base (in celo-monorepo/packages/sdk/base/src/identifier.ts) - * to ensure interoperability with other projects. When adding new prefixes, - * please use either the full platform name in all lowercase (e.g. 'facebook') - * or DID methods https://w3c.github.io/did-spec-registries/#did-methods. - * Make sure to add the expected value for the unit test case in - * `celo-monorepo/packages/sdk/base/src/identifier.test.ts`, - * otherwise the test will fail. - * - * The NULL prefix is included to allow projects to use the sdk without selecting - * a predefined prefix or adding their own. Production use of the NULL prefix is - * discouraged since identifiers will not be interoperable with other projects. - * Please think carefully before using the NULL prefix. - */ -export { IdentifierPrefix } -// Docstring is duplicated in @celo/base; make sure to update in both places. -/** - * Concatenates the identifierPrefix and plaintextIdentifier with the separator '://' - * - * @param plaintextIdentifier Off-chain identifier, ex: phone number, twitter handle, email, etc. - * @param identifierPrefix Standardized prefix used to prevent collisions between identifiers - */ -export { getPrefixedIdentifier } - -/** - * Steps from the private plaintext identifier to the obfuscated identifier, which can be made public. - * - * plaintext identifier: off-chain information, ex: phone number, twitter handle, email, etc. - * blinded identifier: obtained by blinding the plaintext identifier - * blinded signature: blinded identifier signed by ODIS - * unblinded signatue: obtained by unblinding the blinded signature - * pepper: unique secret, obtained by hashing the unblinded signature - * obfuscated identifier: identifier used for on-chain attestations, obtained by hashing the plaintext identifier and pepper - */ - -export interface IdentifierHashDetails { - // plaintext off-chain phone number, twitter handle, email, etc. - plaintextIdentifier: string - // identifier obtained after hashing, used for on-chain attestations - obfuscatedIdentifier: string - // unique pepper obtained through ODIS - pepper: string - // raw signature from ODIS - unblindedSignature?: string -} - -/** - * Retrieve the obfuscated identifier for the provided plaintext identifier - * Performs blinding, querying, and unblinding - * - * @remarks This function will send a request to ODIS, authorized by the provided signer. - * This method consumes ODIS quota on the account provided by the signer. - * You can use the DEK as your signer to decrease quota usage - * - * @param plaintextIdentifier Off-chain identifier, ex: phone number, twitter handle, email, etc. - * @param identifierPrefix Standardized prefix used to prevent collisions between identifiers - * @param account The address making the request to ODIS, from which quota will be charged - * @param signer Object containing the private key used to authenticate the ODIS request - * @param context Specifies which ODIS combiner url should be queried (i.e. mainnet or alfajores) - * @param blindingFactor Optional Private seed used to blind identifers before they are sent to ODIS - * @param clientVersion Optional Specifies the client software version - * @param blsBlindingClient Optional Performs blinding and unblinding, defaults to WasmBlsBlindingClient - * @param sessionID Optional Used to track user sessions across the client and ODIS - * @param keyVersion Optional For testing. Specifies which version key ODIS should use - * @param abortController Optional Allows client to specify a timeout for the ODIS request - */ -export async function getObfuscatedIdentifier( - plaintextIdentifier: string, - identifierPrefix: IdentifierPrefix, - account: string, - signer: AuthSigner, - context: ServiceContext, - blindingFactor?: string, - clientVersion?: string, - blsBlindingClient?: BlsBlindingClient, - sessionID?: string, - keyVersion?: number, - abortController?: AbortController -): Promise { - debug('Getting identifier pepper') - - let seed: Buffer | undefined - if (blindingFactor) { - seed = Buffer.from(blindingFactor) - } else if (signer.authenticationMethod === AuthenticationMethod.ENCRYPTION_KEY) { - seed = Buffer.from((signer as EncryptionKeySigner).rawKey) - } - - // Fallback to using Wasm version if not specified - if (!blsBlindingClient) { - debug('No BLSBlindingClient found, using WasmBlsBlindingClient') - blsBlindingClient = new WasmBlsBlindingClient(context.odisPubKey) - } - - const base64BlindedMessage = await getBlindedIdentifier( - plaintextIdentifier, - identifierPrefix, - blsBlindingClient, - seed - ) - - const base64BlindSig = await getBlindedIdentifierSignature( - account, - signer, - context, - base64BlindedMessage, - clientVersion, - sessionID, - keyVersion, - abortController - ) - - return getObfuscatedIdentifierFromSignature( - plaintextIdentifier, - identifierPrefix, - base64BlindSig, - blsBlindingClient - ) -} - -/** - * Blinds the plaintext identifier in preparation for the ODIS request - * - * @remarks Caller should use the same blsBlindingClient instance for unblinding - * - * @param plaintextIdentifier Off-chain identifier, ex: phone number, twitter handle, email, etc. - * @param identifierPrefix Standardized prefix used to prevent collisions between identifiers - * @param blsBlindingClient Optional Performs blinding and unblinding, defaults to WasmBlsBlindingClient - * @param seed Optional Buffer generated from the blindingFactor, if provided - */ -export async function getBlindedIdentifier( - plaintextIdentifier: string, - identifierPrefix: IdentifierPrefix, - blsBlindingClient: BlsBlindingClient, - seed?: Buffer -): Promise { - debug('Retrieving blinded message') - // phone number identifiers don't have prefixes in the blinding stage - // to maintain backwards compatibility wih ASv1 - let identifier = getPrefixedIdentifier(plaintextIdentifier, identifierPrefix) - if (identifierPrefix === IdentifierPrefix.PHONE_NUMBER) { - if (!isE164Number(plaintextIdentifier)) { - throw new Error(`Invalid phone number: ${plaintextIdentifier}`) - } - identifier = plaintextIdentifier - } - return blsBlindingClient.blindMessage(Buffer.from(identifier).toString('base64'), seed) -} - -/** - * Query ODIS for the blinded signature - * - * @remarks - * Response can be passed into getObfuscatedIdentifierFromSignature - * to retrieve the obfuscated identifier - * - * @param account The address making the request to ODIS, from which quota will be charged - * @param signer Object containing the private key used to authenticate the ODIS request - * @param context Specifies which ODIS combiner url should be queried (i.e. mainnet or alfajores) - * @param base64BlindedMessage The blinded prefixed identifier to be sent to ODIS - * @param clientVersion Optional Specifies the client software version - * @param sessionID Optional Used to track user sessions across the client and ODIS - * @param keyVersion Optional For testing. Specifies which version key ODIS should use - * @param abortController Optional Allows client to specify a timeout for the ODIS request - */ -export async function getBlindedIdentifierSignature( - account: string, - signer: AuthSigner, - context: ServiceContext, - base64BlindedMessage: string, - clientVersion?: string, - sessionID?: string, - keyVersion?: number, - abortControlller?: AbortController -): Promise { - const body: SignMessageRequest = { - account, - blindedQueryPhoneNumber: base64BlindedMessage, - version: clientVersion, - authenticationMethod: signer.authenticationMethod, - sessionID, - } - - const response = await queryOdis( - body, - context, - CombinerEndpointPNP.PNP_SIGN, - SignMessageResponseSchema, - { - [KEY_VERSION_HEADER]: keyVersion?.toString(), - Authorization: await getOdisPnpRequestAuth(body, signer), - }, - abortControlller - ) - - if (!response.success) { - throw new Error(response.error) - } - - return response.signature -} - -/** - * Unblind the response and return the obfuscated identifier - * - * @param plaintextIdentifier Off-chain identifier, ex: phone number, twitter handle, email, etc. - * @param identifierPrefix Standardized prefix used to prevent collisions between identifiers - * @param base64BlindedSignature The blinded signed identifier returned by ODIS - * @param blsBlindingClient Optional Performs blinding and unblinding, defaults to WasmBlsBlindingClient - */ -export async function getObfuscatedIdentifierFromSignature( - plaintextIdentifier: string, - identifierPrefix: IdentifierPrefix, - base64BlindedSignature: string, - blsBlindingClient: BlsBlindingClient -): Promise { - debug('Retrieving unblinded signature') - const base64UnblindedSig = await blsBlindingClient.unblindAndVerifyMessage(base64BlindedSignature) - const sigBuf = Buffer.from(base64UnblindedSig, 'base64') - - debug('Converting sig to pepper') - const pepper = getPepperFromThresholdSignature(sigBuf) - const obfuscatedIdentifier = getIdentifierHash(plaintextIdentifier, identifierPrefix, pepper) - return { - plaintextIdentifier, - obfuscatedIdentifier, - pepper, - unblindedSignature: base64UnblindedSig, - } -} - -/** - * Generates final identifier that is published on-chain. - * - * @remarks - * Concatenates the plaintext prefixed identifier with the pepper derived by hashing the unblinded - * signature returned by ODIS. - * - * @param plaintextIdentifier Off-chain identifier, ex: phone number, twitter handle, email, etc. - * @param identifierPrefix Standardized prefix used to prevent collisions between identifiers - * @param pepper Hash of the unblinded signature returned by ODIS - */ -export const getIdentifierHash = ( - plaintextIdentifier: string, - identifierPrefix: IdentifierPrefix, - pepper: string -): string => { - return baseGetIdentifierHash(sha3, plaintextIdentifier, identifierPrefix, pepper) -} - -/** - * This is the algorithm that creates a pepper from the unblinded message signatures - * It simply hashes it with sha256 and encodes it to hex - * - * @remarks Currently uses 13 chars for a 78 bit pepper - * - * @param sigBuf Unblinded signature returned by ODIS - */ -export function getPepperFromThresholdSignature(sigBuf: Buffer) { - return createHash('sha256').update(sigBuf).digest('base64').slice(0, PEPPER_CHAR_LENGTH) -} diff --git a/packages/sdk/identity/src/odis/index.ts b/packages/sdk/identity/src/odis/index.ts deleted file mode 100644 index b4a93a2e0e..0000000000 --- a/packages/sdk/identity/src/odis/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as BlsBlindingClient from './bls-blinding-client' -import * as CircuitBreaker from './circuit-breaker' -import * as Identifier from './identifier' -import * as PhoneNumberIdentifier from './phone-number-identifier' -import * as Query from './query' -import * as Quota from './quota' - -export const OdisUtils = { - Identifier, - BlsBlindingClient, - Query, - PhoneNumberIdentifier, - CircuitBreaker, - Quota, -} diff --git a/packages/sdk/identity/src/odis/phone-number-identifier.test.ts b/packages/sdk/identity/src/odis/phone-number-identifier.test.ts deleted file mode 100644 index c3418ff0fe..0000000000 --- a/packages/sdk/identity/src/odis/phone-number-identifier.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Endpoint } from '@celo/phone-number-privacy-common' -import { WasmBlsBlindingClient } from './bls-blinding-client' -import { - getBlindedPhoneNumber, - getBlindedPhoneNumberSignature, - getPhoneNumberIdentifier, - getPhoneNumberIdentifierFromSignature, - isBalanceSufficientForSigRetrieval, -} from './phone-number-identifier' -import { AuthenticationMethod, EncryptionKeySigner, ErrorMessages, ServiceContext } from './query' -import fetchMock from '../__mocks__/cross-fetch' - -jest.mock('./bls-blinding-client', () => { - // tslint:disable-next-line:no-shadowed-variable - class WasmBlsBlindingClient { - blindMessage = (m: string) => m - unblindAndVerifyMessage = (m: string) => m - } - return { - WasmBlsBlindingClient, - } -}) - -const mockE164Number = '+14155550000' -const mockAccount = '0x0000000000000000000000000000000000007E57' -const expectedPhoneHash = '0xf3ddadd1f488cdd42b9fa10354fdcae67c303ce182e71b30855733b50dce8301' -const expectedPepper = 'nHIvMC9B4j2+H' - -const serviceContext: ServiceContext = { - odisUrl: 'https://mockodis.com', - odisPubKey: - '7FsWGsFnmVvRfMDpzz95Np76wf/1sPaK0Og9yiB+P8QbjiC8FV67NBans9hzZEkBaQMhiapzgMR6CkZIZPvgwQboAxl65JWRZecGe5V3XO4sdKeNemdAZ2TzQuWkuZoA', -} -const endpoint = serviceContext.odisUrl + Endpoint.PNP_SIGN -const rawKey = '41e8e8593108eeedcbded883b8af34d2f028710355c57f4c10a056b72486aa04' - -const authSigner: EncryptionKeySigner = { - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - rawKey, -} - -describe(isBalanceSufficientForSigRetrieval, () => { - it('identifies sufficient balance correctly', () => { - expect(isBalanceSufficientForSigRetrieval(0.009, 0.004)).toBe(false) - expect(isBalanceSufficientForSigRetrieval(0.01, 0)).toBe(true) - expect(isBalanceSufficientForSigRetrieval(0, 0.005)).toBe(true) - }) -}) - -describe(getPhoneNumberIdentifier, () => { - afterEach(() => { - fetchMock.reset() - }) - - describe('Retrieves a pepper correctly', () => { - it('Using EncryptionKeySigner', async () => { - fetchMock.mock(endpoint, { - success: true, - signature: '0Uj+qoAu7ASMVvm6hvcUGx2eO/cmNdyEgGn0mSoZH8/dujrC1++SZ1N6IP6v2I8A', - performedQueryCount: 5, - totalQuota: 10, - version: '', - }) - - const blsBlindingClient = new WasmBlsBlindingClient(serviceContext.odisPubKey) - const base64BlindedMessage = await getBlindedPhoneNumber(mockE164Number, blsBlindingClient) - const base64BlindSig = await getBlindedPhoneNumberSignature( - mockAccount, - authSigner, - serviceContext, - base64BlindedMessage - ) - const base64UnblindedSig = await blsBlindingClient.unblindAndVerifyMessage(base64BlindSig) - - await expect( - getPhoneNumberIdentifier(mockE164Number, mockAccount, authSigner, serviceContext) - ).resolves.toMatchObject({ - e164Number: mockE164Number, - pepper: expectedPepper, - phoneHash: expectedPhoneHash, - unblindedSignature: base64UnblindedSig, - }) - }) - - it('Preblinding the phone number', async () => { - fetchMock.mock(endpoint, { - success: true, - signature: '0Uj+qoAu7ASMVvm6hvcUGx2eO/cmNdyEgGn0mSoZH8/dujrC1++SZ1N6IP6v2I8A', - performedQueryCount: 5, - totalQuota: 10, - version: '', - }) - - const blsBlindingClient = new WasmBlsBlindingClient(serviceContext.odisPubKey) - const base64BlindedMessage = await getBlindedPhoneNumber(mockE164Number, blsBlindingClient) - - const base64BlindSig = await getBlindedPhoneNumberSignature( - mockAccount, - authSigner, - serviceContext, - base64BlindedMessage - ) - - const phoneNumberHashDetails = await getPhoneNumberIdentifierFromSignature( - mockE164Number, - base64BlindSig, - blsBlindingClient - ) - - expect(phoneNumberHashDetails.phoneHash).toEqual(expectedPhoneHash) - expect(phoneNumberHashDetails.pepper).toEqual(expectedPepper) - }) - }) - - it('Throws quota error', async () => { - fetchMock.mock(endpoint, 403) - - await expect( - getPhoneNumberIdentifier(mockE164Number, mockAccount, authSigner, serviceContext) - ).rejects.toThrow(ErrorMessages.ODIS_QUOTA_ERROR) - }) - - it('Throws auth error', async () => { - fetchMock.mock(endpoint, 401) - await expect( - getPhoneNumberIdentifier(mockE164Number, mockAccount, authSigner, serviceContext) - ).rejects.toThrow(ErrorMessages.ODIS_AUTH_ERROR) - }) -}) diff --git a/packages/sdk/identity/src/odis/phone-number-identifier.ts b/packages/sdk/identity/src/odis/phone-number-identifier.ts deleted file mode 100644 index 97b32d82ae..0000000000 --- a/packages/sdk/identity/src/odis/phone-number-identifier.ts +++ /dev/null @@ -1,141 +0,0 @@ -import BigNumber from 'bignumber.js' -import debugFactory from 'debug' -import { BlsBlindingClient } from './bls-blinding-client' -import { - getBlindedIdentifier, - getBlindedIdentifierSignature, - getObfuscatedIdentifier, - getObfuscatedIdentifierFromSignature, - IdentifierPrefix, -} from './identifier' -import { AuthSigner, ServiceContext } from './query' - -// ODIS minimum dollar balance for sig retrieval -export const ODIS_MINIMUM_DOLLAR_BALANCE = 0.01 -// ODIS minimum celo balance for sig retrieval -export const ODIS_MINIMUM_CELO_BALANCE = 0.005 - -const debug = debugFactory('kit:odis:phone-number-identifier') - -export interface PhoneNumberHashDetails { - e164Number: string - phoneHash: string - pepper: string - unblindedSignature?: string -} - -/** - * Retrieve the on-chain identifier for the provided phone number - * Performs blinding, querying, and unblinding - * @deprecated use getObfuscatedIdentifier instead - */ -export async function getPhoneNumberIdentifier( - e164Number: string, - account: string, - signer: AuthSigner, - context: ServiceContext, - blindingFactor?: string, - clientVersion?: string, - blsBlindingClient?: BlsBlindingClient, - sessionID?: string, - keyVersion?: number -): Promise { - debug('Getting phone number pepper') - - const { plaintextIdentifier, obfuscatedIdentifier, pepper, unblindedSignature } = - await getObfuscatedIdentifier( - e164Number, - IdentifierPrefix.PHONE_NUMBER, - account, - signer, - context, - blindingFactor, - clientVersion, - blsBlindingClient, - sessionID, - keyVersion - ) - return { - e164Number: plaintextIdentifier, - phoneHash: obfuscatedIdentifier, - pepper, - unblindedSignature, - } -} - -/** - * Blinds the phone number in preparation for the ODIS request - * Caller should use the same blsBlindingClient instance for unblinding - * @deprecated use getBlindedIdentifier instead - */ -export async function getBlindedPhoneNumber( - e164Number: string, - blsBlindingClient: BlsBlindingClient, - seed?: Buffer -): Promise { - return getBlindedIdentifier(e164Number, IdentifierPrefix.PHONE_NUMBER, blsBlindingClient, seed) -} - -/** - * Query ODIS for the blinded signature - * Response can be passed into getPhoneNumberIdentifierFromSignature - * to retrieve the on-chain identifier - * @deprecated use getBlindedIdentifierSignature instead - */ -export async function getBlindedPhoneNumberSignature( - account: string, - signer: AuthSigner, - context: ServiceContext, - base64BlindedMessage: string, - clientVersion?: string, - sessionID?: string, - keyVersion?: number -): Promise { - return getBlindedIdentifierSignature( - account, - signer, - context, - base64BlindedMessage, - clientVersion, - sessionID, - keyVersion - ) -} - -/** - * Unblind the response and return the on-chain identifier - * @deprecated use getObfuscatedIdentifieriFromSignature instead - */ -export async function getPhoneNumberIdentifierFromSignature( - e164Number: string, - base64BlindedSignature: string, - blsBlindingClient: BlsBlindingClient -): Promise { - const { plaintextIdentifier, obfuscatedIdentifier, pepper, unblindedSignature } = - await getObfuscatedIdentifierFromSignature( - e164Number, - IdentifierPrefix.PHONE_NUMBER, - base64BlindedSignature, - blsBlindingClient - ) - return { - e164Number: plaintextIdentifier, - phoneHash: obfuscatedIdentifier, - pepper, - unblindedSignature, - } -} - -/** - * Check if balance is sufficient for quota retrieval - * @deprecated use getPnpQuotaStatus instead - */ -export function isBalanceSufficientForSigRetrieval( - dollarBalance: BigNumber.Value, - celoBalance: BigNumber.Value -) { - return ( - new BigNumber(dollarBalance).isGreaterThanOrEqualTo(ODIS_MINIMUM_DOLLAR_BALANCE) || - new BigNumber(celoBalance).isGreaterThanOrEqualTo(ODIS_MINIMUM_CELO_BALANCE) - ) -} diff --git a/packages/sdk/identity/src/odis/query.test.ts b/packages/sdk/identity/src/odis/query.test.ts deleted file mode 100644 index 77ba10f75a..0000000000 --- a/packages/sdk/identity/src/odis/query.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import crypto from 'crypto' -import { hexToBuffer, trimLeading0x } from '../../../base/lib' -import { signWithRawKey } from './query' - -const rawKey = '41e8e8593108eeedcbded883b8af34d2f028710355c57f4c10a056b72486aa04' - -describe(signWithRawKey, () => { - it('Signs message digest', async () => { - const msg = `${'a'.repeat(64)} + ${'b'.repeat(16)}` - - // NOTE: Elliptic will truncate the raw msg to 64 bytes before signing, - // so make sure to always pass the hex encoded msgDigest instead. - const msgDigest = crypto.createHash('sha256').update(JSON.stringify(msg)).digest('hex') - - // NOTE: elliptic is disabled elsewhere in this library to prevent - // accidental signing of truncated messages. - // tslint:disable-next-line:import-blacklist - const EC = require('elliptic').ec - const ec = new EC('secp256k1') - - // Sign - const key = ec.keyFromPrivate(hexToBuffer(rawKey)) - const expectedSig = JSON.stringify(key.sign(msgDigest).toDER()) - const receivedSig = signWithRawKey(msg, rawKey) - const badSig = JSON.stringify(key.sign(msg).toDER()) - - // Verify - const pub = key.getPublic(true, 'hex') - const pubKey = ec.keyFromPublic(trimLeading0x(pub), 'hex') - const isValid = (input: string, sig: string) => pubKey.verify(input, JSON.parse(sig)) - expect(isValid(msgDigest, expectedSig)).toBeTruthy() - expect(isValid(msg, badSig)).toBeTruthy() - expect(isValid(msg, expectedSig)).toBeFalsy() - expect(isValid(msg, receivedSig)).toBeFalsy() - expect(isValid(msgDigest, receivedSig)).toBeTruthy() - expect(receivedSig).toEqual(expectedSig) - }) - - it('Signs full message', async () => { - const msg1 = `${'a'.repeat(64)} + ${'b'.repeat(16)}` - const msg2 = `${'a'.repeat(64)} + ${'c'.repeat(16)}` - - const sig1 = signWithRawKey(msg1, rawKey) - const sig2 = signWithRawKey(msg2, rawKey) - - // NOTE: Elliptic will truncate the raw msg to 64 bytes before signing, - // so make sure to always pass the hex encoded msgDigest instead. - const msgDigest1 = crypto.createHash('sha256').update(JSON.stringify(msg1)).digest('hex') - const msgDigest2 = crypto.createHash('sha256').update(JSON.stringify(msg2)).digest('hex') - - // NOTE: elliptic is disabled elsewhere in this library to prevent - // accidental signing of truncated messages. - // tslint:disable-next-line:import-blacklist - const EC = require('elliptic').ec - const ec = new EC('secp256k1') - - // Sign - const key = ec.keyFromPrivate(hexToBuffer(rawKey)) - - // Verify - const pub = key.getPublic(true, 'hex') - const pubKey = ec.keyFromPublic(trimLeading0x(pub), 'hex') - const isValid = (input: string, sig: string) => pubKey.verify(input, JSON.parse(sig)) - expect(isValid(msgDigest1, sig1)).toBeTruthy() - expect(isValid(msgDigest2, sig2)).toBeTruthy() - expect(isValid(msgDigest1, sig2)).toBeFalsy() - expect(isValid(msgDigest2, sig1)).toBeFalsy() - }) -}) diff --git a/packages/sdk/identity/src/odis/query.ts b/packages/sdk/identity/src/odis/query.ts deleted file mode 100644 index 8cbdf00e39..0000000000 --- a/packages/sdk/identity/src/odis/query.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { selectiveRetryAsyncWithBackOff } from '@celo/base/lib/async' -import { ContractKit } from '@celo/contractkit' -import { - AuthenticationMethod, - CombinerEndpoint, - DomainEndpoint, - DomainRequest, - DomainRequestHeader, - DomainResponse, - OdisRequest, - OdisRequestHeader, - OdisResponse, - PhoneNumberPrivacyRequest, - signWithRawKey, -} from '@celo/phone-number-privacy-common' -import fetch from 'cross-fetch' -import debugFactory from 'debug' -import { isLeft } from 'fp-ts/lib/Either' -import * as t from 'io-ts' - -const debug = debugFactory('kit:odis:query') - -export interface WalletKeySigner { - authenticationMethod: AuthenticationMethod.WALLET_KEY - contractKit: ContractKit -} - -export interface EncryptionKeySigner { - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY - rawKey: string -} - -// Support signing with the DEK or with the -export type AuthSigner = WalletKeySigner | EncryptionKeySigner - -// Re-export types and aliases to maintain backwards compatibility. -export { AuthenticationMethod, PhoneNumberPrivacyRequest, signWithRawKey } - -export enum ErrorMessages { - ODIS_QUOTA_ERROR = 'odisQuotaError', - ODIS_RATE_LIMIT_ERROR = 'odisRateLimitError', - ODIS_INPUT_ERROR = 'odisBadInputError', - ODIS_AUTH_ERROR = 'odisAuthError', - ODIS_CLIENT_ERROR = 'Unknown Client Error', - ODIS_FETCH_ERROR = 'odisFetchError', - ODIS_RESPONSE_ERROR = 'odisResponseError', -} - -export interface ServiceContext { - odisUrl: string // combiner url - odisPubKey: string -} - -export const ODIS_STAGING_CONTEXT: ServiceContext = { - odisUrl: 'https://us-central1-celo-phone-number-privacy-stg.cloudfunctions.net/combiner', - odisPubKey: - '7FsWGsFnmVvRfMDpzz95Np76wf/1sPaK0Og9yiB+P8QbjiC8FV67NBans9hzZEkBaQMhiapzgMR6CkZIZPvgwQboAxl65JWRZecGe5V3XO4sdKeNemdAZ2TzQuWkuZoA', -} - -export const ODIS_ALFAJORES_CONTEXT_PNP: ServiceContext = { - odisUrl: 'https://us-central1-celo-phone-number-privacy.cloudfunctions.net/combiner', - odisPubKey: - 'kPoRxWdEdZ/Nd3uQnp3FJFs54zuiS+ksqvOm9x8vY6KHPG8jrfqysvIRU0wtqYsBKA7SoAsICMBv8C/Fb2ZpDOqhSqvr/sZbZoHmQfvbqrzbtDIPvUIrHgRS0ydJCMsA', -} - -export const ODIS_ALFAJORES_CONTEXT_DOMAINS: ServiceContext = { - odisUrl: 'https://us-central1-celo-phone-number-privacy.cloudfunctions.net/combiner', - odisPubKey: - '+ZrxyPvLChWUX/DyPw6TuGwQH0glDJEbSrSxUARyP5PuqYyP/U4WZTV1e0bAUioBZ6QCJMiLpDwTaFvy8VnmM5RBbLQUMrMg5p4+CBCqj6HhsMfcyUj8V0LyuNdStlCB', -} - -export const ODIS_MAINNET_CONTEXT_PNP: ServiceContext = { - odisUrl: 'https://us-central1-celo-pgpnp-mainnet.cloudfunctions.net/combiner', - odisPubKey: - 'FvreHfLmhBjwxHxsxeyrcOLtSonC9j7K3WrS4QapYsQH6LdaDTaNGmnlQMfFY04Bp/K4wAvqQwO9/bqPVCKf8Ze8OZo8Frmog4JY4xAiwrsqOXxug11+htjEe1pj4uMA', -} - -export const ODIS_MAINNET_CONTEXT_DOMAINS: ServiceContext = { - odisUrl: 'https://us-central1-celo-pgpnp-mainnet.cloudfunctions.net/combiner', - odisPubKey: - 'LX4tLiuYm8geZ3ztmH7oIWz4ohXt3ePRTd9BbG9RO86NMrApflioiOzKYtIsyjEA0uarnX8Emo+luTY4bwEWpgZDyPYE6UMWAoBaZBdy6NDMgAxSbdNtaQEq51fBjCUA', -} - -export enum OdisAPI { - PNP = 'pnp', - DOMAIN = 'domain', -} - -export enum OdisContextName { - STAGING = 'alfajoresstaging', - ALFAJORES = 'alfajores', - MAINNET = 'mainnet', -} - -export function getServiceContext( - contextName: OdisContextName = OdisContextName.MAINNET, - api: OdisAPI = OdisAPI.PNP -) { - switch (contextName) { - case OdisContextName.ALFAJORES: - return { - [OdisAPI.PNP]: ODIS_ALFAJORES_CONTEXT_PNP, - [OdisAPI.DOMAIN]: ODIS_ALFAJORES_CONTEXT_DOMAINS, - }[api] - case OdisContextName.STAGING: - return { - // Intentionally the same on staging - [OdisAPI.PNP]: ODIS_STAGING_CONTEXT, - [OdisAPI.DOMAIN]: ODIS_STAGING_CONTEXT, - }[api] - case OdisContextName.MAINNET: - return { - [OdisAPI.PNP]: ODIS_MAINNET_CONTEXT_PNP, - [OdisAPI.DOMAIN]: ODIS_MAINNET_CONTEXT_DOMAINS, - }[api] - default: - return ODIS_MAINNET_CONTEXT_PNP - } -} - -export function signWithDEK(msg: string, signer: EncryptionKeySigner) { - return signWithRawKey(msg, signer.rawKey) -} - -export async function getOdisPnpRequestAuth( - body: PhoneNumberPrivacyRequest, - signer: AuthSigner -): Promise { - // Sign payload using provided account and authentication method. - const bodyString = JSON.stringify(body) - if (signer.authenticationMethod === AuthenticationMethod.ENCRYPTION_KEY) { - return signWithDEK(bodyString, signer as EncryptionKeySigner) - } - if (signer.authenticationMethod === AuthenticationMethod.WALLET_KEY) { - return signer.contractKit.connection.sign(bodyString, body.account) - } - throw new Error('AuthenticationMethod not supported') -} - -/** - * Send any OdisRequest to the specified CombinerEndpoint for the given ServiceContext - * - * @param body OdisRequest to send in the body of the HTTP request. - * @param context Contains service URL and public to determine which instance to contact. - * @param endpoint Endpoint to query - * @param responseSchema io-ts schema to ensure type safety of responses - * @param headers custom request headers corresponding to the type of OdisRequest (keyVersion, Authentication, etc.) - */ -export async function queryOdis( - body: R, - context: ServiceContext, - endpoint: CombinerEndpoint, - responseSchema: t.Type, OdisResponse, unknown>, - headers: OdisRequestHeader, - abortController?: AbortController -): Promise> { - debug(`Posting to ${endpoint}`) - - const dontRetry = [ - ErrorMessages.ODIS_QUOTA_ERROR, - ErrorMessages.ODIS_RATE_LIMIT_ERROR, - ErrorMessages.ODIS_AUTH_ERROR, - ErrorMessages.ODIS_INPUT_ERROR, - ErrorMessages.ODIS_CLIENT_ERROR, - ] - - return selectiveRetryAsyncWithBackOff( - async () => { - let res: Response - try { - res = await fetch(context.odisUrl + endpoint, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - ...headers, - }, - body: JSON.stringify(body), - signal: abortController?.signal, - }) - } catch (error) { - throw new Error(`${ErrorMessages.ODIS_FETCH_ERROR}: ${error}`) - } - - if (res.ok) { - debug('Response ok. Parsing.') - const response = await res.json() - - // Verify that the response is the type we expected, then return it. - const decoding = responseSchema.decode(response) - if (isLeft(decoding)) { - throw new Error(ErrorMessages.ODIS_RESPONSE_ERROR) - } - return decoding.right - } - - debug(`Response not okay. Status ${res.status}`) - - switch (res.status) { - case 403: - throw new Error(ErrorMessages.ODIS_QUOTA_ERROR) - case 429: - throw new Error(ErrorMessages.ODIS_RATE_LIMIT_ERROR) - case 400: - throw new Error(ErrorMessages.ODIS_INPUT_ERROR) - case 401: - throw new Error(ErrorMessages.ODIS_AUTH_ERROR) - default: - if (res.status >= 400 && res.status < 500) { - // Don't retry error codes in 400s - throw new Error(`${ErrorMessages.ODIS_CLIENT_ERROR} ${res.status}`) - } - throw new Error(`Unknown failure ${res.status}`) - } - }, - 3, - dontRetry, - [] - ) -} - -/** - * Send the given domain request to ODIS (e.g. to get a POPRF evaluation or check quota). - * - * @param body Request to send in the body of the HTTP request. - * @param context Contains service URL and public to determine which instance to contact. - * @param endpoint Endpoint to query (e.g. '/domain/sign', '/domain/quotaStatus'). - * @param responseSchema io-ts type for the expected response type. Provided to ensure type safety. - * @param headers optional header fields relevant to the given request type (keyVersion, Authentication, etc.) - */ -export async function sendOdisDomainRequest( - body: R, - context: ServiceContext, - endpoint: DomainEndpoint, - responseSchema: t.Type>, - headers?: DomainRequestHeader -): Promise> { - return queryOdis( - body, - context, - endpoint, - responseSchema, - headers as OdisRequestHeader - ) as Promise> -} diff --git a/packages/sdk/identity/src/odis/quota.test.ts b/packages/sdk/identity/src/odis/quota.test.ts deleted file mode 100644 index 5c2e34efb1..0000000000 --- a/packages/sdk/identity/src/odis/quota.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { AuthenticationMethod, CombinerEndpoint } from '@celo/phone-number-privacy-common' -import fetchMock from '../__mocks__/cross-fetch' -import { EncryptionKeySigner, ServiceContext } from './query' -import { getPnpQuotaStatus, PnpClientQuotaStatus } from './quota' - -const mockAccount = '0x0000000000000000000000000000000000007E57' -const serviceContext: ServiceContext = { - odisUrl: 'https://mockodis.com', - odisPubKey: - '7FsWGsFnmVvRfMDpzz95Np76wf/1sPaK0Og9yiB+P8QbjiC8FV67NBans9hzZEkBaQMhiapzgMR6CkZIZPvgwQboAxl65JWRZecGe5V3XO4sdKeNemdAZ2TzQuWkuZoA', -} -const endpoint = serviceContext.odisUrl + CombinerEndpoint.PNP_QUOTA -const rawKey = '41e8e8593108eeedcbded883b8af34d2f028710355c57f4c10a056b72486aa04' - -const authSigner: EncryptionKeySigner = { - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - rawKey, -} - -describe(getPnpQuotaStatus, () => { - afterEach(() => { - fetchMock.reset() - }) - it('returns the correct remaining quota amount', async () => { - const performedQueryCount = 5 - const totalQuota = 10 - const version = '' - fetchMock.mock(endpoint, { - success: true, - totalQuota, - performedQueryCount, - version, - }) - - await expect( - getPnpQuotaStatus(mockAccount, authSigner, serviceContext) - ).resolves.toStrictEqual({ - performedQueryCount, - totalQuota, - remainingQuota: totalQuota - performedQueryCount, - version, - warnings: undefined, - }) - }) - - it('throws quota error on failure response', async () => { - fetchMock.mock(endpoint, { - success: false, - version: '', - }) - - await expect(getPnpQuotaStatus(mockAccount, authSigner, serviceContext)).rejects.toThrow() - }) -}) diff --git a/packages/sdk/identity/src/odis/quota.ts b/packages/sdk/identity/src/odis/quota.ts deleted file mode 100644 index 8d866b9ce3..0000000000 --- a/packages/sdk/identity/src/odis/quota.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Address } from '@celo/base' -import { - CombinerEndpoint, - PnpQuotaRequest, - PnpQuotaResponseSchema, -} from '@celo/phone-number-privacy-common' -import { AuthSigner, getOdisPnpRequestAuth, queryOdis, ServiceContext } from './query' - -export interface PnpClientQuotaStatus { - version: string - performedQueryCount: number - totalQuota: number - remainingQuota: number - blockNumber?: number // TODO fully remove blockNumber from identity sdk - warnings?: string[] -} - -/** - * Query the ODIS quota status of a given account - * - * @param account The address whose ODIS quota we are querying - * @param signer Object containing the private key used to authenticate the ODIS request - * @param context Specifies which ODIS combiner url should be queried (i.e. mainnet or alfajores) - * @param clientVersion Optional Specifies the client software version - * @param sessionID Optional Used to track user sessions across the client and ODIS - * @param abortController Optional Allows client to specify a timeout for the ODIS request - */ -export async function getPnpQuotaStatus( - account: Address, - signer: AuthSigner, - context: ServiceContext, - clientVersion?: string, - sessionID?: string, - abortController?: AbortController -): Promise { - const body: PnpQuotaRequest = { - account, - version: clientVersion, - authenticationMethod: signer.authenticationMethod, - sessionID, - } - - const response = await queryOdis( - body, - context, - CombinerEndpoint.PNP_QUOTA, - PnpQuotaResponseSchema, - { - Authorization: await getOdisPnpRequestAuth(body, signer), - }, - abortController - ) - - if (response.success) { - return { - version: response.version, - performedQueryCount: response.performedQueryCount, - totalQuota: response.totalQuota, - remainingQuota: response.totalQuota - response.performedQueryCount, - warnings: response.warnings, - } - } - - throw new Error(response.error) -} diff --git a/packages/sdk/identity/src/offchain-data-wrapper.test.ts b/packages/sdk/identity/src/offchain-data-wrapper.test.ts deleted file mode 100644 index aa26010c45..0000000000 --- a/packages/sdk/identity/src/offchain-data-wrapper.test.ts +++ /dev/null @@ -1,376 +0,0 @@ -import { Result } from '@celo/base' -import { ContractKit, newKitFromWeb3 } from '@celo/contractkit' -import { createStorageClaim } from '@celo/contractkit/lib/identity/claims/claim' -import { IdentityMetadataWrapper } from '@celo/contractkit/lib/identity/metadata' -import { AccountsWrapper } from '@celo/contractkit/lib/wrappers/Accounts' -import { ACCOUNT_PRIVATE_KEYS } from '@celo/dev-utils/lib/ganache-setup' -import { testWithGanache } from '@celo/dev-utils/lib/ganache-test' -import { - ensureLeading0x, - privateKeyToAddress, - privateKeyToPublicKey, - publicKeyToAddress, - toChecksumAddress, -} from '@celo/utils/lib/address' -import { ensureCompressed } from '@celo/utils/lib/ecdh' -import { NativeSigner, serializeSignature } from '@celo/utils/lib/signatureUtils' -import { LocalWallet } from '@celo/wallet-local' -import { randomBytes } from 'crypto' -import { BasicDataWrapper, OffchainDataWrapper, OffchainErrorTypes } from './offchain-data-wrapper' -import { AuthorizedSignerAccessor } from './offchain/accessors/authorized-signer' -import { SchemaErrors, SchemaErrorTypes } from './offchain/accessors/errors' -import { PrivateNameAccessor, PublicNameAccessor } from './offchain/accessors/name' -import { MockStorageWriter } from './offchain/storage-writers' -import fetchMock from './__mocks__/cross-fetch' - -const testname = 'test' -const testPayload = { name: testname } - -interface RegisteredAccount { - wrapper: OffchainDataWrapper - privateKey: string - publicKey: string - address: string - storageRoot: string - localStorageRoot: string - kit: ContractKit -} - -testWithGanache('Offchain Data', (web3) => { - const kit = newKitFromWeb3(web3, new LocalWallet()) - - const writerPrivate = ACCOUNT_PRIVATE_KEYS[0] - const readerPrivate = ACCOUNT_PRIVATE_KEYS[1] - const reader2Private = ACCOUNT_PRIVATE_KEYS[2] - const signerPrivate = ACCOUNT_PRIVATE_KEYS[3] - - const writerEncryptionKeyPrivate = ensureLeading0x(randomBytes(32).toString('hex')) - const readerEncryptionKeyPrivate = ensureLeading0x(randomBytes(32).toString('hex')) - const reader2EncryptionKeyPrivate = ensureLeading0x(randomBytes(32).toString('hex')) - - let accounts: AccountsWrapper - let writer: RegisteredAccount - let reader: RegisteredAccount - let reader2: RegisteredAccount - let signer: RegisteredAccount - - async function setupAccount( - privateKey: string, - dek?: string, - compressedDEK = false - ): Promise { - const publicKey = privateKeyToPublicKey(privateKey) - const address = publicKeyToAddress(publicKey) - const metadataURL = `http://example.com/${address}/metadata` - const storageRoot = `http://example.com/${address}/root` - const localStorageRoot = `/tmp/offchain/${address}` - - accounts = await kit.contracts.getAccounts() - await accounts.createAccount().sendAndWaitForReceipt({ from: address }) - - if (dek) { - await accounts - .setAccountDataEncryptionKey( - compressedDEK - ? ensureLeading0x(ensureCompressed(privateKeyToPublicKey(dek))) - : privateKeyToPublicKey(dek) - ) - .sendAndWaitForReceipt({ from: address }) - kit.connection.addAccount(dek) - } - - const metadata = IdentityMetadataWrapper.fromEmpty(address) - await metadata.addClaim(createStorageClaim(storageRoot), NativeSigner(web3.eth.sign, address)) - - fetchMock.mock(metadataURL, metadata.toString()) - await accounts.setMetadataURL(metadataURL).sendAndWaitForReceipt({ from: address }) - - kit.connection.addAccount(privateKey) - - const wrapper = new BasicDataWrapper(address, kit) - wrapper.storageWriter = new MockStorageWriter(localStorageRoot, storageRoot, fetchMock) - - return { wrapper, privateKey, publicKey, address, storageRoot, localStorageRoot, kit } - } - - beforeEach(async () => { - writer = await setupAccount(writerPrivate, writerEncryptionKeyPrivate) - reader = await setupAccount(readerPrivate, readerEncryptionKeyPrivate) - reader2 = await setupAccount(reader2Private, reader2EncryptionKeyPrivate) - signer = await setupAccount(signerPrivate) - }) - - afterEach(() => { - fetchMock.reset() - writer.kit.getWallet()!.removeAccount(writer.address) - reader.kit.getWallet()!.removeAccount(reader.address) - reader2.kit.getWallet()!.removeAccount(reader2.address) - signer.kit.getWallet()!.removeAccount(signer.address) - }) - - const assertValidNameResponse = (resp: Result<{ name: string }, SchemaErrors>) => { - if (resp.ok) { - expect(resp.result.name).toEqual(testname) - } else { - const error = resp.error - switch (error.errorType) { - case SchemaErrorTypes.InvalidDataError: - console.log("Something was wrong with the schema, can't try again") - break - case SchemaErrorTypes.OffchainError: - const offchainError = error.error - switch (offchainError.errorType) { - case OffchainErrorTypes.FetchError: - console.log('Something went wrong with fetching, try again') - break - case OffchainErrorTypes.InvalidSignature: - console.log('Signature was wrong') - break - case OffchainErrorTypes.NoStorageRootProvidedData: - console.log("Account doesn't have data for this type") - break - } - - default: - break - } - throw new Error(error.message) - } - } - - describe('with the account being the signer', () => { - it('can write a name', async () => { - const nameAccessor = new PublicNameAccessor(writer.wrapper) - await nameAccessor.write(testPayload) - - const readerNameAccessor = new PublicNameAccessor(reader.wrapper) - const resp = await readerNameAccessor.readAsResult(writer.address) - assertValidNameResponse(resp) - }) - }) - - describe('with the DEK being the signer', () => { - it('can write a name', async () => { - const writerPrivateKey = ACCOUNT_PRIVATE_KEYS[5] - const writerDEK = randomBytes(32).toString('hex') - const compressedWriter = await setupAccount(writerPrivateKey, writerDEK, true) - const DEKAddress = privateKeyToAddress(writerDEK) - compressedWriter.wrapper.kit.connection.addAccount(writerDEK) - compressedWriter.wrapper.signer = DEKAddress - - const nameAccessor = new PublicNameAccessor(compressedWriter.wrapper) - await nameAccessor.write(testPayload) - - const readerNameAccessor = new PublicNameAccessor(reader.wrapper) - const resp = await readerNameAccessor.readAsResult(compressedWriter.address) - assertValidNameResponse(resp) - }) - }) - - it('cannot write with a signer that is not authorized', async () => { - // Mock the 404 - fetchMock.mock( - writer.storageRoot + `/account/authorizedSigners/${toChecksumAddress(signer.address)}`, - 404 - ) - - const wrapper = new BasicDataWrapper(signer.address, kit) - wrapper.storageWriter = new MockStorageWriter( - writer.localStorageRoot, - writer.storageRoot, - fetchMock - ) - const nameAccessor = new PublicNameAccessor(wrapper) - await nameAccessor.write(testPayload) - - const receivedName = await nameAccessor.readAsResult(writer.address) - expect(receivedName.ok).toEqual(false) - const authorizedSignerAccessor = new AuthorizedSignerAccessor(writer.wrapper) - const authorization = await authorizedSignerAccessor.readAsResult( - writer.address, - signer.address - ) - expect(authorization.ok).toEqual(false) - }) - - describe('with a different key being authorized to sign off-chain', () => { - beforeEach(async () => { - const pop = await accounts.generateProofOfKeyPossession(writer.address, signer.address) - const authorizedSignerAccessor = new AuthorizedSignerAccessor(writer.wrapper) - await authorizedSignerAccessor.write(signer.address, serializeSignature(pop), '.*') - }) - - it('can read the authorization', async () => { - const authorizedSignerAccessor = new AuthorizedSignerAccessor(reader.wrapper) - const authorization = await authorizedSignerAccessor.readAsResult( - writer.address, - signer.address - ) - expect(authorization).toBeDefined() - }) - - it('can write a name', async () => { - const nameAccessor = new PublicNameAccessor(signer.wrapper) - await nameAccessor.write(testPayload) - - const readerNameAccessor = new PublicNameAccessor(reader.wrapper) - const resp = await readerNameAccessor.readAsResult(writer.address) - if (resp.ok) { - expect(resp.result.name).toEqual(testname) - } - }) - }) - - describe('with a reader that has a dataEncryptionKey registered', () => { - it('encrypted data can be read and written', async () => { - const writerNameAccessor = new PrivateNameAccessor(writer.wrapper) - await writerNameAccessor.write(testPayload, [reader.address]) - - const readerNameAccessor = new PrivateNameAccessor(reader.wrapper) - const receivedName = await readerNameAccessor.readAsResult(writer.address) - - if (receivedName.ok) { - expect(receivedName.result.name).toEqual(testname) - } else { - console.error(receivedName.error) - throw new Error('should not get here') - } - }) - - it('trying to read encrypted data without an encrypted accessor fails', async () => { - const nameAccessor = new PrivateNameAccessor(writer.wrapper) - await nameAccessor.write(testPayload, [reader.address]) - - const readerNameAccessor = new PublicNameAccessor(reader.wrapper) - const receivedName = await readerNameAccessor.readAsResult(writer.address) - - if (receivedName.ok) { - throw new Error('Should not be able to read data without encrypted name accessor') - } - expect(receivedName.error.errorType).toBe(SchemaErrorTypes.OffchainError) - // @ts-ignore - expect(receivedName.error.error.errorType).toBe(OffchainErrorTypes.NoStorageRootProvidedData) - }) - - it('can re-encrypt data to more recipients', async () => { - const nameAccessor = new PrivateNameAccessor(writer.wrapper) - await nameAccessor.write(testPayload, [reader.address]) - await nameAccessor.allowAccess([reader2.address]) - - const readerNameAccessor = new PrivateNameAccessor(reader.wrapper) - const receivedName = await readerNameAccessor.readAsResult(writer.address) - const reader2NameAccessor = new PrivateNameAccessor(reader2.wrapper) - const receivedName2 = await reader2NameAccessor.readAsResult(writer.address) - - if (receivedName.ok && receivedName2.ok) { - expect(receivedName.result.name).toEqual(testname) - expect(receivedName2.result.name).toEqual(testname) - } else { - throw new Error('should not get here') - } - }) - - it('can encrypt data with user defined symmetric key', async () => { - const symmetricKey = randomBytes(16) - - const nameAccessor = new PrivateNameAccessor(writer.wrapper) - await nameAccessor.write(testPayload, [reader.address], symmetricKey) - - const readerNameAccessor = new PrivateNameAccessor(reader.wrapper) - const receivedName = await readerNameAccessor.readAsResult(writer.address) - - if (receivedName.ok) { - expect(receivedName.result.name).toEqual(testname) - } else { - console.error(receivedName.error) - throw new Error('should not get here') - } - }) - - describe('when the key is not added to the wallet', () => { - beforeEach(() => { - reader.kit - .getWallet()! - .removeAccount(publicKeyToAddress(privateKeyToPublicKey(readerEncryptionKeyPrivate))) - }) - - it('the reader cannot decrypt the data', async () => { - const nameAccessor = new PrivateNameAccessor(writer.wrapper) - await nameAccessor.write(testPayload, [reader.address]) - - const readerNameAccessor = new PrivateNameAccessor(reader.wrapper) - const receivedName = await readerNameAccessor.readAsResult(writer.address) - - if (receivedName.ok) { - throw new Error('Should not get here') - } - - expect(receivedName.error.errorType).toEqual(SchemaErrorTypes.UnavailableKey) - }) - }) - }) - - describe('when data encryption keys are compressed', () => { - it('works when the writer has a compressed key', async () => { - const writerPrivateKey = ACCOUNT_PRIVATE_KEYS[4] - const writerDEK = randomBytes(32).toString('hex') - const compressedWriter = await setupAccount(writerPrivateKey, writerDEK, true) - compressedWriter.wrapper.kit.connection.addAccount(writerDEK) - - const writerNameAccessor = new PrivateNameAccessor(compressedWriter.wrapper) - const readerNameAccessor = new PrivateNameAccessor(reader.wrapper) - - await writerNameAccessor.write(testPayload, [reader.address]) - const receivedName = await readerNameAccessor.readAsResult(compressedWriter.address) - if (receivedName.ok) { - expect(receivedName.result.name).toEqual(testname) - } else { - console.error(receivedName.error) - throw new Error('should not get here') - } - }) - - it('works when the reader has a compressed key', async () => { - const readerPrivateKey = ACCOUNT_PRIVATE_KEYS[7] - const readerDEK = randomBytes(32).toString('hex') - const compressedReader = await setupAccount(readerPrivateKey, readerDEK, true) - compressedReader.wrapper.kit.connection.addAccount(readerDEK) - - const writerNameAccessor = new PrivateNameAccessor(writer.wrapper) - const readerNameAccessor = new PrivateNameAccessor(compressedReader.wrapper) - - await writerNameAccessor.write(testPayload, [compressedReader.address]) - const receivedName = await readerNameAccessor.readAsResult(writer.address) - if (receivedName.ok) { - expect(receivedName.result.name).toEqual(testname) - } else { - console.error(receivedName.error) - throw new Error('should not get here') - } - }) - - it('works when both the reader and the writer have compressed keys', async () => { - const writerPrivateKey = ACCOUNT_PRIVATE_KEYS[8] - const writerDEK = randomBytes(32).toString('hex') - const compressedWriter = await setupAccount(writerPrivateKey, writerDEK, true) - compressedWriter.wrapper.kit.connection.addAccount(writerDEK) - - const readerPrivateKey = ACCOUNT_PRIVATE_KEYS[9] - const readerDEK = randomBytes(32).toString('hex') - const compressedReader = await setupAccount(readerPrivateKey, readerDEK, true) - compressedReader.wrapper.kit.connection.addAccount(readerDEK) - - const writerNameAccessor = new PrivateNameAccessor(compressedWriter.wrapper) - const readerNameAccessor = new PrivateNameAccessor(compressedReader.wrapper) - - await writerNameAccessor.write(testPayload, [compressedReader.address]) - const receivedName = await readerNameAccessor.readAsResult(compressedWriter.address) - if (receivedName.ok) { - expect(receivedName.result.name).toEqual(testname) - } else { - console.error(receivedName.error) - throw new Error('should not get here') - } - }) - }) -}) diff --git a/packages/sdk/identity/src/offchain-data-wrapper.ts b/packages/sdk/identity/src/offchain-data-wrapper.ts deleted file mode 100644 index 8a1e5c72eb..0000000000 --- a/packages/sdk/identity/src/offchain-data-wrapper.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { Address, ensureLeading0x } from '@celo/base/lib/address' -import { Err, makeAsyncThrowable, Ok, Result, RootError } from '@celo/base/lib/result' -import { ContractKit } from '@celo/contractkit' -import { ClaimTypes } from '@celo/contractkit/lib/identity/claims/types' -import { IdentityMetadataWrapper } from '@celo/contractkit/lib/identity/metadata' -import { publicKeyToAddress } from '@celo/utils/lib/address' -import { ensureUncompressed } from '@celo/utils/lib/ecdh' -import { - recoverEIP712TypedDataSignerRsv, - recoverEIP712TypedDataSignerVrs, - verifyEIP712TypedDataSigner, -} from '@celo/utils/lib/signatureUtils' -import fetch from 'cross-fetch' -import debugFactory from 'debug' -import * as t from 'io-ts' -import { AuthorizedSignerAccessor } from './offchain/accessors/authorized-signer' -import { StorageWriter } from './offchain/storage-writers' -import { buildEIP712TypedData, resolvePath } from './offchain/utils' - -const debug = debugFactory('offchaindata') - -export enum OffchainErrorTypes { - FetchError = 'FetchError', - InvalidSignature = 'InvalidSignature', - NoStorageRootProvidedData = 'NoStorageRootProvidedData', - NoStorageProvider = 'NoStorageProvider', -} - -export class FetchError extends RootError { - constructor(error: Error) { - super(OffchainErrorTypes.FetchError) - this.message = error.message - } -} - -export class InvalidSignature extends RootError { - constructor() { - super(OffchainErrorTypes.InvalidSignature) - } -} - -export class NoStorageRootProvidedData extends RootError { - constructor() { - super(OffchainErrorTypes.NoStorageRootProvidedData) - } -} - -export class NoStorageProvider extends RootError { - constructor() { - super(OffchainErrorTypes.NoStorageProvider) - } -} - -export type OffchainErrors = - | FetchError - | InvalidSignature - | NoStorageRootProvidedData - | NoStorageProvider - -export interface OffchainDataWrapper { - kit: ContractKit - signer: Address - self: Address - writeDataTo(data: Buffer, signature: Buffer, dataPath: string): Promise - readDataFromAsResult( - account: Address, - dataPath: string, - checkOffchainSigners: boolean, - type?: t.Type - ): Promise> -} - -export class BasicDataWrapper implements OffchainDataWrapper { - storageWriter: StorageWriter | undefined - signer: string - - constructor(readonly self: string, readonly kit: ContractKit, signer?: string) { - this.signer = signer || self - } - - async readDataFromAsResult( - account: Address, - dataPath: string, - checkOffchainSigners: boolean, - type?: t.Type - ): Promise> { - const accounts = await this.kit.contracts.getAccounts() - const metadataURL = await accounts.getMetadataURL(account) - debug({ account, metadataURL }) - const metadata = await IdentityMetadataWrapper.fetchFromURL(accounts, metadataURL) - // TODO: Filter StorageRoots with the datapath glob - const storageRoots = metadata - .filterClaims(ClaimTypes.STORAGE) - .map((_) => new StorageRoot(this, account, _.address)) - - if (storageRoots.length === 0) { - return Err(new NoStorageRootProvidedData()) - } - - const results = await Promise.all( - storageRoots.map(async (s) => s.readAndVerifySignature(dataPath, checkOffchainSigners, type)) - ) - const item = results.find((s) => s.ok) - - if (item === undefined) { - return Err(new NoStorageRootProvidedData()) - } - - return item - } - - readDataFrom = makeAsyncThrowable(this.readDataFromAsResult.bind(this)) - - async writeDataTo( - data: Buffer, - signature: Buffer, - dataPath: string - ): Promise { - if (this.storageWriter === undefined) { - return new NoStorageProvider() - } - - try { - await Promise.all([ - this.storageWriter.write(data, dataPath), - this.storageWriter.write(signature, `${dataPath}.signature`), - ]) - } catch (e: any) { - return new FetchError(e instanceof Error ? e : new Error(e)) - } - } -} - -class StorageRoot { - constructor( - readonly wrapper: OffchainDataWrapper, - readonly account: Address, - readonly root: string - ) {} - - async readAndVerifySignature( - dataPath: string, - checkOffchainSigners: boolean, - type?: t.Type - ): Promise> { - let dataResponse, signatureResponse - - try { - ;[dataResponse, signatureResponse] = await Promise.all([ - fetch(resolvePath(this.root, dataPath)), - fetch(resolvePath(this.root, `${dataPath}.signature`)), - ]) - } catch (error: any) { - const fetchError = error instanceof Error ? error : new Error(error) - return Err(new FetchError(fetchError)) - } - - if (!dataResponse.ok) { - return Err(new FetchError(new Error(dataResponse.statusText))) - } - if (!signatureResponse.ok) { - return Err(new FetchError(new Error(signatureResponse.statusText))) - } - - const [dataBody, signatureBody] = await Promise.all([ - dataResponse.arrayBuffer(), - signatureResponse.arrayBuffer(), - ]) - const body = Buffer.from(dataBody) - const signature = ensureLeading0x(Buffer.from(signatureBody).toString('hex')) - - const toParse = type ? JSON.parse(body.toString()) : body - const typedData = await buildEIP712TypedData(this.wrapper, dataPath, toParse, type) - - if (verifyEIP712TypedDataSigner(typedData, signature, this.account)) { - return Ok(body) - } - - const accounts = await this.wrapper.kit.contracts.getAccounts() - if (await accounts.isAccount(this.account)) { - const keys = await Promise.all([ - accounts.getVoteSigner(this.account), - accounts.getValidatorSigner(this.account), - accounts.getAttestationSigner(this.account), - accounts.getDataEncryptionKey(this.account), - ]) - - const dekAddress = keys[3] ? publicKeyToAddress(ensureUncompressed(keys[3])) : '0x0' - const signers = [keys[0], keys[1], keys[2], dekAddress] - - if (signers.some((signer) => verifyEIP712TypedDataSigner(typedData, signature, signer))) { - return Ok(body) - } - - if (checkOffchainSigners) { - let guessedSigner: string - try { - guessedSigner = recoverEIP712TypedDataSignerRsv(typedData, signature) - } catch (error) { - guessedSigner = recoverEIP712TypedDataSignerVrs(typedData, signature) - } - const authorizedSignerAccessor = new AuthorizedSignerAccessor(this.wrapper) - const authorizedSigner = await authorizedSignerAccessor.readAsResult( - this.account, - guessedSigner - ) - if (authorizedSigner.ok) { - return Ok(body) - } - } - } - - return Err(new InvalidSignature()) - } -} diff --git a/packages/sdk/identity/src/offchain/accessors/authorized-signer.ts b/packages/sdk/identity/src/offchain/accessors/authorized-signer.ts deleted file mode 100644 index 366480b804..0000000000 --- a/packages/sdk/identity/src/offchain/accessors/authorized-signer.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Address, trimLeading0x } from '@celo/base' -import { Err, makeAsyncThrowable } from '@celo/base/lib/result' -import { toChecksumAddress } from '@celo/utils/lib/address' -import { AddressType, SignatureType } from '@celo/utils/lib/io' -import * as t from 'io-ts' -import { OffchainDataWrapper, OffchainErrors } from '../../offchain-data-wrapper' -import { buildEIP712TypedData, deserialize } from '../utils' -import { OffchainError } from './errors' - -const AuthorizedSignerSchema = t.type({ - address: AddressType, - proofOfPossession: SignatureType, - filteredDataPaths: t.string, -}) - -export class AuthorizedSignerAccessor { - basePath = '/account/authorizedSigners' - constructor(readonly wrapper: OffchainDataWrapper) {} - - async readAsResult(account: Address, signer: Address) { - const dataPath = this.basePath + '/' + toChecksumAddress(signer) - const rawData = await this.wrapper.readDataFromAsResult( - account, - dataPath, - false, - AuthorizedSignerSchema - ) - if (!rawData.ok) { - return Err(new OffchainError(rawData.error)) - } - - return deserialize(AuthorizedSignerSchema, rawData.result) - } - - read = makeAsyncThrowable(this.readAsResult.bind(this)) - - async write( - signer: Address, - proofOfPossession: string, - filteredDataPaths: string - ): Promise { - const payload = { - address: toChecksumAddress(signer), - proofOfPossession, - filteredDataPaths, - } - const dataPath = this.basePath + '/' + toChecksumAddress(signer) - const typedData = await buildEIP712TypedData( - this.wrapper, - dataPath, - payload, - AuthorizedSignerSchema - ) - const signature = await this.wrapper.kit - .getWallet()! - .signTypedData(this.wrapper.self, typedData) - return this.wrapper.writeDataTo( - Buffer.from(JSON.stringify(payload)), - Buffer.from(trimLeading0x(signature), 'hex'), - dataPath - ) - } -} diff --git a/packages/sdk/identity/src/offchain/accessors/binary.ts b/packages/sdk/identity/src/offchain/accessors/binary.ts deleted file mode 100644 index 71cfb41e03..0000000000 --- a/packages/sdk/identity/src/offchain/accessors/binary.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Address, trimLeading0x } from '@celo/base/lib/address' -import { Err, makeAsyncThrowable, Ok } from '@celo/base/lib/result' -import { OffchainDataWrapper } from '../../offchain-data-wrapper' -import { readEncrypted, signBuffer, writeEncrypted, writeSymmetricKeys } from '../utils' -import { OffchainError } from './errors' -import { PrivateAccessor, PublicAccessor } from './interfaces' - -/** - * Schema for writing any generic binary data - */ -export class PublicBinaryAccessor implements PublicAccessor { - constructor(readonly wrapper: OffchainDataWrapper, readonly dataPath: string) {} - - async write(data: Buffer) { - const signature = await signBuffer(this.wrapper, this.dataPath, data) - const error = await this.wrapper.writeDataTo( - data, - Buffer.from(trimLeading0x(signature), 'hex'), - this.dataPath - ) - if (error) { - return new OffchainError(error) - } - } - - async readAsResult(account: Address) { - const rawData = await this.wrapper.readDataFromAsResult(account, this.dataPath, true) - if (!rawData.ok) { - return Err(new OffchainError(rawData.error)) - } - - return Ok(rawData.result) - } - - read = makeAsyncThrowable(this.readAsResult.bind(this)) -} - -/** - * Schema for writing any encrypted binary data. - */ -export class PrivateBinaryAccessor implements PrivateAccessor { - constructor(readonly wrapper: OffchainDataWrapper, readonly dataPath: string) {} - - async write(data: Buffer, toAddresses: Address[], symmetricKey?: Buffer) { - return writeEncrypted(this.wrapper, this.dataPath, data, toAddresses, symmetricKey) - } - - async allowAccess(toAddresses: Address[], symmetricKey?: Buffer) { - return writeSymmetricKeys(this.wrapper, this.dataPath, toAddresses, symmetricKey) - } - - async readAsResult(account: Address) { - return readEncrypted(this.wrapper, this.dataPath, account) - } - - read = makeAsyncThrowable(this.readAsResult.bind(this)) -} diff --git a/packages/sdk/identity/src/offchain/accessors/errors.ts b/packages/sdk/identity/src/offchain/accessors/errors.ts deleted file mode 100644 index 0edddc8ed0..0000000000 --- a/packages/sdk/identity/src/offchain/accessors/errors.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Address } from '@celo/base' -import { RootError } from '@celo/base/lib/result' -import { OffchainErrors } from '../../offchain-data-wrapper' - -export enum SchemaErrorTypes { - InvalidDataError = 'InvalidDataError', - OffchainError = 'OffchainError', - UnknownCiphertext = 'UnknownCiphertext', - UnavailableKey = 'UnavailableKey', - InvalidKey = 'InvalidKey', -} - -export class InvalidDataError extends RootError { - constructor() { - super(SchemaErrorTypes.InvalidDataError) - } -} - -export class OffchainError extends RootError { - constructor(readonly error: OffchainErrors) { - super(SchemaErrorTypes.OffchainError) - } -} - -export class UnknownCiphertext extends RootError { - constructor() { - super(SchemaErrorTypes.UnknownCiphertext) - } -} - -export class UnavailableKey extends RootError { - constructor(readonly account: Address) { - super(SchemaErrorTypes.UnavailableKey) - this.message = `Unable to find account ${account}` - } -} - -export class InvalidKey extends RootError { - constructor() { - super(SchemaErrorTypes.InvalidKey) - } -} - -export type SchemaErrors = - | InvalidDataError - | OffchainError - | UnknownCiphertext - | UnavailableKey - | InvalidKey diff --git a/packages/sdk/identity/src/offchain/accessors/interfaces.ts b/packages/sdk/identity/src/offchain/accessors/interfaces.ts deleted file mode 100644 index c73ac43806..0000000000 --- a/packages/sdk/identity/src/offchain/accessors/interfaces.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Result } from '@celo/base' -import { SchemaErrors } from './errors' - -export interface PublicAccessor { - write: (data: DataType) => Promise - read: (from: string) => Promise - readAsResult: (from: string) => Promise> -} - -export interface PrivateAccessor { - write: (data: DataType, to: string[], symmetricKey?: Buffer) => Promise - read: (from: string) => Promise - readAsResult: (from: string) => Promise> -} diff --git a/packages/sdk/identity/src/offchain/accessors/name.ts b/packages/sdk/identity/src/offchain/accessors/name.ts deleted file mode 100644 index ca4d53eb71..0000000000 --- a/packages/sdk/identity/src/offchain/accessors/name.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as t from 'io-ts' -import { OffchainDataWrapper } from '../../offchain-data-wrapper' -import { PrivateSimpleAccessor, PublicSimpleAccessor } from './simple' - -const NameSchema = t.type({ - name: t.string, -}) - -export type NameType = t.TypeOf - -export class PublicNameAccessor extends PublicSimpleAccessor { - constructor(readonly wrapper: OffchainDataWrapper) { - super(wrapper, NameSchema, '/account/name') - } -} - -export class PrivateNameAccessor extends PrivateSimpleAccessor { - constructor(readonly wrapper: OffchainDataWrapper) { - super(wrapper, NameSchema, '/account/name') - } -} diff --git a/packages/sdk/identity/src/offchain/accessors/pictures.ts b/packages/sdk/identity/src/offchain/accessors/pictures.ts deleted file mode 100644 index cf60ecd0c2..0000000000 --- a/packages/sdk/identity/src/offchain/accessors/pictures.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { OffchainDataWrapper } from '../../offchain-data-wrapper' -import { PrivateBinaryAccessor, PublicBinaryAccessor } from './binary' - -export class PublicPictureAccessor extends PublicBinaryAccessor { - constructor(readonly wrapper: OffchainDataWrapper) { - super(wrapper, '/account/picture') - } -} - -export class PrivatePictureAccessor extends PrivateBinaryAccessor { - constructor(readonly wrapper: OffchainDataWrapper) { - super(wrapper, '/account/picture') - } -} diff --git a/packages/sdk/identity/src/offchain/accessors/simple.ts b/packages/sdk/identity/src/offchain/accessors/simple.ts deleted file mode 100644 index 4089900c1a..0000000000 --- a/packages/sdk/identity/src/offchain/accessors/simple.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Address, trimLeading0x } from '@celo/base' -import { Err, makeAsyncThrowable, Result } from '@celo/base/lib/result' -import * as t from 'io-ts' -import { OffchainDataWrapper } from '../../offchain-data-wrapper' -import { - buildEIP712TypedData, - deserialize, - readEncrypted, - writeEncrypted, - writeSymmetricKeys, -} from '../utils' -import { InvalidDataError, OffchainError, SchemaErrors } from './errors' -import { PrivateAccessor, PublicAccessor } from './interfaces' - -function serialize(data: DataType) { - return Buffer.from(JSON.stringify(data)) -} - -/** - * A generic schema for reading and writing objects to and from storage. Passing - * in a type parameter is supported for runtime type safety. - */ -export class PublicSimpleAccessor implements PublicAccessor { - constructor( - readonly wrapper: OffchainDataWrapper, - readonly type: t.Type, - readonly dataPath: string - ) {} - - private async sign(data: DataType) { - const typedData = await buildEIP712TypedData(this.wrapper, this.dataPath, data, this.type) - const wallet = this.wrapper.kit.getWallet()! - return wallet.signTypedData(this.wrapper.signer, typedData) - } - - async write(data: DataType) { - if (!this.type.is(data)) { - return new InvalidDataError() - } - - const signature = await this.sign(data) - const error = await this.wrapper.writeDataTo( - serialize(data), - Buffer.from(trimLeading0x(signature), 'hex'), - this.dataPath - ) - if (error) { - return new OffchainError(error) - } - } - - async readAsResult(account: Address): Promise> { - const rawData = await this.wrapper.readDataFromAsResult(account, this.dataPath, true, this.type) - - if (!rawData.ok) { - return Err(new OffchainError(rawData.error)) - } - - const deserializedResult = deserialize(this.type, rawData.result) - if (deserializedResult.ok) { - return deserializedResult - } - - return deserializedResult - } - - read = makeAsyncThrowable(this.readAsResult.bind(this)) -} - -/** - * A generic schema for writing and reading encrypted objects to and from storage. Passing - * in a type parameter is supported for runtime type safety. - */ -export class PrivateSimpleAccessor implements PrivateAccessor { - constructor( - readonly wrapper: OffchainDataWrapper, - readonly type: t.Type, - readonly dataPath: string - ) {} - - write(data: DataType, toAddresses: Address[], symmetricKey?: Buffer) { - if (!this.type.is(data)) { - return Promise.resolve(new InvalidDataError()) - } - - return writeEncrypted(this.wrapper, this.dataPath, serialize(data), toAddresses, symmetricKey) - } - - async allowAccess(toAddresses: Address[], symmetricKey?: Buffer) { - return writeSymmetricKeys(this.wrapper, this.dataPath, toAddresses, symmetricKey) - } - - async readAsResult(account: Address): Promise> { - const encryptedResult = await readEncrypted(this.wrapper, this.dataPath, account) - - if (encryptedResult.ok) { - return deserialize(this.type, encryptedResult.result) - } - - return encryptedResult - } - - read = makeAsyncThrowable(this.readAsResult.bind(this)) -} diff --git a/packages/sdk/identity/src/offchain/storage-writers.ts b/packages/sdk/identity/src/offchain/storage-writers.ts deleted file mode 100644 index 3b6fc8bbe4..0000000000 --- a/packages/sdk/identity/src/offchain/storage-writers.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { spawnSync } from 'child_process' -import { promises } from 'fs' -import { join, parse } from 'path' -import { resolvePath } from './utils' -export abstract class StorageWriter { - abstract write(_data: Buffer, _dataPath: string): Promise -} - -export class LocalStorageWriter extends StorageWriter { - constructor(readonly root: string) { - super() - } - async write(data: Buffer, dataPath: string): Promise { - return this.writeToFs(data, dataPath) - } - - protected async writeToFs(data: string | Buffer, dataPath: string): Promise { - const directory = parse(dataPath).dir - await promises.mkdir(join(this.root, directory), { recursive: true }) - await promises.writeFile(join(this.root, dataPath), data) - } -} - -export class GitStorageWriter extends LocalStorageWriter { - async write(data: Buffer, dataPath: string): Promise { - await this.writeToFs(data, dataPath) - spawnSync('git', ['add', dataPath], { - cwd: this.root, - }) - spawnSync('git', ['commit', '--message', `"Upload ${dataPath}"`], { cwd: this.root }) - spawnSync('git', ['push', 'origin', 'master'], { cwd: this.root }) - return - } -} - -export class GoogleStorageWriter extends LocalStorageWriter { - private readonly bucket: string - - constructor(readonly local: string, bucket: string) { - super(local) - this.bucket = bucket - } - - async write(data: Buffer, dataPath: string): Promise { - await this.writeToFs(data, dataPath) - spawnSync('gsutil', ['cp', join(this.root, dataPath), `gs://${this.bucket}${dataPath}`], { - cwd: this.root, - }) - } -} - -export class AwsStorageWriter extends LocalStorageWriter { - private readonly bucket: string - - constructor(readonly local: string, bucket: string) { - super(local) - this.bucket = bucket - } - - async write(data: Buffer, dataPath: string): Promise { - await this.writeToFs(data, dataPath) - spawnSync('aws', ['s3', 'cp', join(this.root, dataPath), `s3://${this.bucket}${dataPath}`], { - cwd: this.root, - }) - } -} - -export class MockStorageWriter extends LocalStorageWriter { - constructor(readonly root: string, readonly mockedStorageRoot: string, readonly fetchMock: any) { - super(root) - } - async write(data: Buffer, dataPath: string): Promise { - await this.writeToFs(data, dataPath) - this.fetchMock.mock(resolvePath(this.mockedStorageRoot, dataPath), data, { - sendAsJson: false, - overwriteRoutes: true, - }) - } -} diff --git a/packages/sdk/identity/src/offchain/utils.ts b/packages/sdk/identity/src/offchain/utils.ts deleted file mode 100644 index af8674bbdc..0000000000 --- a/packages/sdk/identity/src/offchain/utils.ts +++ /dev/null @@ -1,407 +0,0 @@ -import { ensureLeading0x, Err, Ok, parseJsonAsResult, Result, trimLeading0x } from '@celo/base' -import { Address, publicKeyToAddress } from '@celo/utils/lib/address' -import { ensureCompressed, ensureUncompressed, trimUncompressedPrefix } from '@celo/utils/lib/ecdh' -import { AES128Decrypt, AES128Encrypt, Encrypt, IV_LENGTH } from '@celo/utils/lib/ecies' -import { EIP712Object, EIP712TypedData } from '@celo/utils/lib/sign-typed-data-utils' -import { createHmac, randomBytes } from 'crypto' -import { keccak256 } from 'ethereum-cryptography/keccak' -import { toHex } from 'ethereum-cryptography/utils' -import { isLeft } from 'fp-ts/lib/Either' -import * as t from 'io-ts' -import { join, sep } from 'path' -import { OffchainDataWrapper, OffchainErrorTypes } from '../offchain-data-wrapper' -import { - InvalidDataError, - InvalidKey, - OffchainError, - SchemaErrors, - SchemaErrorTypes, - UnavailableKey, -} from './accessors/errors' - -const KEY_LENGTH = 16 - -// label = PRF(ECDH(A, B), A || B || data path) -// ciphertext path = "/cosmetic path/" || base64(label) -function getCiphertextLabel( - path: string, - sharedSecret: Buffer, - senderPublicKey: string, - receiverPublicKey: string -) { - const senderPublicKeyBuffer = Buffer.from(ensureCompressed(senderPublicKey), 'hex') - const receiverPublicKeyBuffer = Buffer.from(ensureCompressed(receiverPublicKey), 'hex') - - const label = createHmac('sha256', sharedSecret) - .update(Buffer.concat([senderPublicKeyBuffer, receiverPublicKeyBuffer, Buffer.from(path)])) - .digest('hex') - return join(sep, 'ciphertexts', label) -} - -// Assumes that the wallet has the dataEncryptionKey of wrapper.self available -// TODO: Should check and throw a more meaningful error if not - -/** - * Encrypts the symmetric key `key` to `toAddress`'s data encryption key and uploads it - * to the computed storage path. - * - * @param wrapper the offchain data wrapper - * @param dataPath logical path for the data. Used to derive the key location - * @param key the symmetric key to distribute - * @param toAddress address to encrypt symmetric key to - */ -const distributeSymmetricKey = async ( - wrapper: OffchainDataWrapper, - dataPath: string, - key: Buffer, - toAddress: Address -): Promise => { - const accounts = await wrapper.kit.contracts.getAccounts() - const [fromPubKey, toPubKey] = await Promise.all([ - accounts.getDataEncryptionKey(wrapper.self), - accounts.getDataEncryptionKey(toAddress), - ]) - if (fromPubKey === null) { - return new UnavailableKey(wrapper.self) - } - if (toPubKey === null) { - return new UnavailableKey(toAddress) - } - - const wallet = wrapper.kit.getWallet()! - const sharedSecret = await wallet.computeSharedSecret(publicKeyToAddress(fromPubKey), toPubKey) - - const computedDataPath = getCiphertextLabel(`${dataPath}.key`, sharedSecret, fromPubKey, toPubKey) - const encryptedData = Encrypt( - Buffer.from(trimUncompressedPrefix(ensureUncompressed(toPubKey)), 'hex'), - key - ) - - const signature = await signBuffer(wrapper, computedDataPath, encryptedData) - const writeError = await wrapper.writeDataTo( - encryptedData, - Buffer.from(trimLeading0x(signature), 'hex'), - computedDataPath - ) - if (writeError) { - return new OffchainError(writeError) - } -} - -/** - * Handles choosing the symmetric key to use. - * If we're explicitly passing in a key, use that, - * If a key has already been generated for this dataPath, use that, - * Else generate a new one - * - * @param wrapper the offchain data wrapper - * @param dataPath path to where the encrypted data is stored. Used to derive the key location - * @param symmetricKey - */ -async function fetchOrGenerateKey( - wrapper: OffchainDataWrapper, - dataPath: string, - symmetricKey?: Buffer -) { - if (symmetricKey) { - return Ok(symmetricKey) - } - - const existingKey = await readSymmetricKey(wrapper, dataPath, wrapper.self) - if (existingKey.ok) { - return Ok(existingKey.result) - } - - if ( - existingKey.error.errorType === SchemaErrorTypes.OffchainError && - (existingKey.error.error.errorType === OffchainErrorTypes.NoStorageRootProvidedData || - existingKey.error.error.errorType === OffchainErrorTypes.FetchError) - ) { - return Ok(randomBytes(16)) - } - - return Err(existingKey.error) -} - -/** - * Handles encrypting the data with a symmetric key, then distributing said key to each address - * in the `toAddresses` array. - * - * @param wrapper the offchain data wrapper - * @param dataPath path to where the encrypted data is stored. Used to derive the key location - * @param data the data to encrypt - * @param toAddresses the addresses to distribute the symmetric key to - * @param symmetricKey the symmetric key to use to encrypt the data. One will be found or generated if not provided - */ -export const writeEncrypted = async ( - wrapper: OffchainDataWrapper, - dataPath: string, - data: Buffer, - toAddresses: Address[], - symmetricKey?: Buffer -): Promise => { - const fetchKey = await fetchOrGenerateKey(wrapper, dataPath, symmetricKey) - if (!fetchKey.ok) { - return fetchKey.error - } - - const iv = randomBytes(16) - const payload = AES128Encrypt(fetchKey.result, iv, data) - const signature = await signBuffer(wrapper, `${dataPath}.enc`, payload) - - const writeError = await wrapper.writeDataTo( - payload, - Buffer.from(trimLeading0x(signature), 'hex'), - `${dataPath}.enc` - ) - if (writeError) { - return new OffchainError(writeError) - } - - const firstWriteError = ( - await Promise.all( - // here we encrypt the key to ourselves so we can retrieve it later - [wrapper.self, ...toAddresses].map(async (toAddress) => - distributeSymmetricKey(wrapper, dataPath, fetchKey.result, toAddress) - ) - ) - ).find(Boolean) - return firstWriteError -} - -export const writeSymmetricKeys = async ( - wrapper: OffchainDataWrapper, - dataPath: string, - toAddresses: Address[], - symmetricKey?: Buffer -): Promise => { - const fetchKey = await fetchOrGenerateKey(wrapper, dataPath, symmetricKey) - if (!fetchKey.ok) { - return fetchKey.error - } - - const firstWriteError = ( - await Promise.all( - toAddresses.map(async (toAddress) => - distributeSymmetricKey(wrapper, dataPath, fetchKey.result, toAddress) - ) - ) - ).find(Boolean) - return firstWriteError -} - -/** - * Reads and decrypts a symmetric key that has been encrypted to your - * data encryption key. - * - * @param wrapper the offchain data wrapper - * @param dataPath path to where the encrypted data is stored. Used to derive the key location - * @param senderAddress the address that encrypted this key to you - */ -const readSymmetricKey = async ( - wrapper: OffchainDataWrapper, - dataPath: string, - senderAddress: Address -): Promise> => { - const accounts = await wrapper.kit.contracts.getAccounts() - const wallet = wrapper.kit.getWallet()! - const [readerPubKey, senderPubKey] = await Promise.all([ - accounts.getDataEncryptionKey(wrapper.self), - accounts.getDataEncryptionKey(senderAddress), - ]) - - if (readerPubKey === null) { - return Err(new UnavailableKey(wrapper.self)) - } - if (senderPubKey === null) { - return Err(new UnavailableKey(senderAddress)) - } - - const readerPublicKeyAddress = publicKeyToAddress(readerPubKey) - if (!wallet.hasAccount(readerPublicKeyAddress)) { - return Err(new UnavailableKey(readerPublicKeyAddress)) - } - - const sharedSecret = await wallet.computeSharedSecret(readerPublicKeyAddress, senderPubKey) - const computedDataPath = getCiphertextLabel( - `${dataPath}.key`, - sharedSecret, - senderPubKey, - readerPubKey - ) - const encryptedPayload = await wrapper.readDataFromAsResult(senderAddress, computedDataPath, true) - - if (!encryptedPayload.ok) { - return Err(new OffchainError(encryptedPayload.error)) - } - - const payload = await wallet.decrypt(readerPublicKeyAddress, encryptedPayload.result) - return Ok(payload) -} - -/** - * Reads and decrypts a payload that has been encrypted to your data encryption key. Will - * resolve the symmetric key used to encrypt the payload. - * - * @param wrapper the offchain data wrapper - * @param dataPath path to where the encrypted data is stored. Used to derive the key location - * @param senderAddress the address that encrypted this key to you - */ -export const readEncrypted = async ( - wrapper: OffchainDataWrapper, - dataPath: string, - senderAddress: Address -): Promise> => { - const encryptedPayloadPath = `${dataPath}.enc` - const [payload, key] = await Promise.all([ - wrapper.readDataFromAsResult(senderAddress, encryptedPayloadPath, true), - readSymmetricKey(wrapper, dataPath, senderAddress), - ]) - - if (!payload.ok) { - return Err(new OffchainError(payload.error)) - } - if (!key.ok) { - return Err(key.error) - } - - if (key.result.length !== KEY_LENGTH) { - return Err(new InvalidKey()) - } - if (payload.result.length < IV_LENGTH) { - return Err(new InvalidDataError()) - } - - return Ok( - AES128Decrypt(key.result, payload.result.slice(0, IV_LENGTH), payload.result.slice(IV_LENGTH)) - ) -} - -export const deserialize = ( - type: t.Type, - buf: Buffer -): Result => { - const dataAsJson = parseJsonAsResult(buf.toString()) - if (!dataAsJson.ok) { - return Err(new InvalidDataError()) - } - - const parsedDataAsType = type.decode(dataAsJson.result) - if (isLeft(parsedDataAsType)) { - return Err(new InvalidDataError()) - } - - return Ok(parsedDataAsType.right) -} - -export const buildEIP712TypedData = async ( - wrapper: OffchainDataWrapper, - path: string, - data: DataType | Buffer, - type?: t.Type -): Promise => { - const chainId = await wrapper.kit.connection.chainId() - const EIP712Domain = [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - ] - - let types = {} - let message = {} - if (Buffer.isBuffer(data)) { - types = { - ClaimWithPath: [ - { name: 'path', type: 'string' }, - { name: 'hash', type: 'string' }, - ], - } - message = { - hash: ensureLeading0x(toHex(keccak256(data))), - } - } else { - const Claim = buildEIP712Schema(type!) - types = { - Claim, - ClaimWithPath: [ - { name: 'path', type: 'string' }, - { name: 'payload', type: 'Claim' }, - ], - } - message = { - payload: data as unknown as EIP712Object, - } - } - - return { - types: { - EIP712Domain, - ...types, - }, - domain: { - name: 'CIP8 Claim', - version: '1.0.0', - chainId, - }, - primaryType: 'ClaimWithPath', - message: { - path, - ...message, - }, - } -} - -export const signBuffer = async (wrapper: OffchainDataWrapper, dataPath: string, buf: Buffer) => { - const typedData = await buildEIP712TypedData(wrapper, dataPath, buf) - return wrapper.kit.getWallet()!.signTypedData(wrapper.signer, typedData) -} - -const ioTsToSolidityTypeMapping: { [x: string]: string } = { - [t.string._tag]: 'string', - [t.number._tag]: 'uint256', - [t.boolean._tag]: 'bool', -} - -type EIP712Schema = Array<{ name: string; type: string }> -const buildEIP712Schema = (type: t.Type): EIP712Schema => { - // @ts-ignore - const shape = type.props - // @ts-ignore - return Object.entries(shape).reduce((accum, [key, value]) => { - // @ts-ignore - return [ - ...accum, - { - name: key, - // @ts-ignore - type: ioTsToSolidityTypeMapping[value._tag] || 'string', - }, - ] - }, []) -} - -function ensureTrailingSeparator(str: string) { - if (str[str.length - 1] !== '/') { - return `${str}/` - } - return str -} -function trimLeadingSeparator(str: string) { - if (str[0] === '/') { - return str.slice(1) - } - return str -} - -/** - * We want users to be able to specify a root + path as their base - * storage url, https://example.com/store-under/path, for example. Constructing - * a URL doesn't respect these paths if the appended path is absolute, so we ensure - * it's not and ensure the base is - * - * @param base root or base of the domain - * @param path the path to append - */ -export function resolvePath(base: string, path: string) { - return new URL(trimLeadingSeparator(path), ensureTrailingSeparator(base)).href -} diff --git a/packages/sdk/identity/src/test-utils/setup.global.ts b/packages/sdk/identity/src/test-utils/setup.global.ts deleted file mode 100644 index 68422038d5..0000000000 --- a/packages/sdk/identity/src/test-utils/setup.global.ts +++ /dev/null @@ -1,23 +0,0 @@ -import baseSetup from '@celo/dev-utils/lib/ganache-setup' -// Has to import the matchers somewhere so that typescript knows the matchers have been made available -import _unused from '@celo/dev-utils/lib/matchers' -import { waitForPortOpen } from '@celo/dev-utils/lib/network' -import * as path from 'path' - -// Warning: There should be an unused import of '@celo/dev-utils/lib/matchers' above. -// If there is not, then your editor probably deleted it automatically. - -const USE_GANACHE = process.env.NO_GANACHE?.toLowerCase() !== 'true' - -export default async function globalSetup() { - if (USE_GANACHE) { - console.log('\nstarting ganache... set NO_GANACHE=true to disable') - await baseSetup(path.resolve(path.join(__dirname, '../..')), '.tmp/devchain.tar.gz', { - from_targz: true, - }) - await waitForPortOpen('localhost', 8545, 60) - console.log('...ganache started') - } else { - console.log('skipping ganache setup') - } -} diff --git a/packages/sdk/identity/src/test-utils/teardown.global.ts b/packages/sdk/identity/src/test-utils/teardown.global.ts deleted file mode 100644 index dcdc64102a..0000000000 --- a/packages/sdk/identity/src/test-utils/teardown.global.ts +++ /dev/null @@ -1,9 +0,0 @@ -import teardown from '@celo/dev-utils/lib/ganache-teardown' - -const USE_GANACHE = process.env.NO_GANACHE?.toLowerCase() !== 'true' - -export default async function globalTeardown() { - if (USE_GANACHE) { - await teardown() - } -} diff --git a/packages/sdk/identity/tsconfig.json b/packages/sdk/identity/tsconfig.json deleted file mode 100644 index b15de29290..0000000000 --- a/packages/sdk/identity/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "@celo/typescript/tsconfig.library.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "lib" - }, - "include": ["src/**/*", "types/**/*"], - "exclude": ["**/*.test.ts"] -} diff --git a/packages/sdk/identity/tslint.json b/packages/sdk/identity/tslint.json deleted file mode 100644 index e3916a756e..0000000000 --- a/packages/sdk/identity/tslint.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": ["@celo/typescript/tslint.json"], - "rules": { - "no-global-arrow-functions": false, - "no-console": false, - "member-ordering": false, - "max-classes-per-file": false, - "import-blacklist": [true, { "elliptic": ["ec"]}] - } -} diff --git a/packages/sdk/identity/typedoc.json b/packages/sdk/identity/typedoc.json deleted file mode 100644 index ff159d9983..0000000000 --- a/packages/sdk/identity/typedoc.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "mode": "modules", - "exclude": ["**/generated/*.ts", "**/*+(index|.test).ts"], - "excludeNotExported": true, - "excludePrivate": true, - "excludeProtected": true, - "includeDeclarations": false, - "ignoreCompilerErrors": true, - "hideGenerator": "true", - "out": "../../docs/sdk/docs/identity", - "gitRevision": "master", - "readme": "none" - } \ No newline at end of file diff --git a/scripts/deploy-sdks.ts b/scripts/deploy-sdks.ts index 06ea10948e..a809dac201 100644 --- a/scripts/deploy-sdks.ts +++ b/scripts/deploy-sdks.ts @@ -58,19 +58,6 @@ type Answers = { ;(async function () { prompt.start() - const { confirmNoDanglingDevs } = await prompt.get([ - { - name: 'confirmNoDanglingDevs', - description: colors.yellow( - `Please ensure the @celo/phone-number-privacy-* packages in any sdk/**/package.json are using a published version. Y/N` - ), - }, - ]) - - if (confirmNoDanglingDevs.toString().toUpperCase() !== 'Y') { - process.exit(1) - } - // `getAnswers` will either prompt the user for a version and whether // or not to publish or it will use an existing failedSDKs.json file. const { packages, version, publish } = await getAnswers() diff --git a/yarn.lock b/yarn.lock index 4f044aac7a..ee5a0f0148 100644 --- a/yarn.lock +++ b/yarn.lock @@ -285,16 +285,6 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@apidevtools/json-schema-ref-parser@^9.0.3": - version "9.1.2" - resolved "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz" - integrity sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg== - dependencies: - "@jsdevtools/ono" "^7.1.3" - "@types/json-schema" "^7.0.6" - call-me-maybe "^1.0.1" - js-yaml "^4.1.0" - "@apollo/protobufjs@1.2.6": version "1.2.6" resolved "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.6.tgz" @@ -410,15 +400,6 @@ dependencies: tslib "^2.2.0" -"@azure/core-auth@^1.1.4": - version "1.5.0" - resolved "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz" - integrity sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw== - dependencies: - "@azure/abort-controller" "^1.0.0" - "@azure/core-util" "^1.1.0" - tslib "^2.2.0" - "@azure/core-auth@^1.3.0", "@azure/core-auth@^1.4.0": version "1.4.0" resolved "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz" @@ -504,14 +485,6 @@ "@azure/abort-controller" "^1.0.0" tslib "^2.2.0" -"@azure/core-util@^1.1.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@azure/core-util/-/core-util-1.4.0.tgz" - integrity sha512-eGAyJpm3skVQoLiRqm/xPa+SXi/NPDdSHMxbRAz2lSprd+Zs+qrpQGQQ2VQ3Nttu+nSZR4XoYQC71LbEI7jsig== - dependencies: - "@azure/abort-controller" "^1.0.0" - tslib "^2.2.0" - "@azure/identity@^1.1.0": version "1.5.2" resolved "https://registry.npmjs.org/@azure/identity/-/identity-1.5.2.tgz" @@ -577,34 +550,6 @@ dependencies: tslib "^2.2.0" -"@azure/ms-rest-azure-env@^2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@azure/ms-rest-azure-env/-/ms-rest-azure-env-2.0.0.tgz" - integrity sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw== - -"@azure/ms-rest-js@^2.0.4": - version "2.7.0" - resolved "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-2.7.0.tgz" - integrity sha512-ngbzWbqF+NmztDOpLBVDxYM+XLcUj7nKhxGbSU9WtIsXfRB//cf2ZbAG5HkOrhU9/wd/ORRB6lM/d69RKVjiyA== - dependencies: - "@azure/core-auth" "^1.1.4" - abort-controller "^3.0.0" - form-data "^2.5.0" - node-fetch "^2.6.7" - tslib "^1.10.0" - tunnel "0.0.6" - uuid "^8.3.2" - xml2js "^0.5.0" - -"@azure/ms-rest-nodeauth@^3.0.10": - version "3.1.1" - resolved "https://registry.npmjs.org/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-3.1.1.tgz" - integrity sha512-UA/8dgLy3+ZiwJjAZHxL4MUB14fFQPkaAOZ94jsTW/Z6WmoOeny2+cLk0+dyIX/iH6qSrEWKwbStEeB970B9pA== - dependencies: - "@azure/ms-rest-azure-env" "^2.0.0" - "@azure/ms-rest-js" "^2.0.4" - adal-node "^0.2.2" - "@azure/msal-common@^4.0.0": version "4.5.1" resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-4.5.1.tgz" @@ -786,11 +731,6 @@ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz" integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw== -"@babel/parser@^7.20.15": - version "7.22.16" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz" - integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== - "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" @@ -961,11 +901,6 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@celo/base@1.5.2": - version "1.5.2" - resolved "https://registry.npmjs.org/@celo/base/-/base-1.5.2.tgz" - integrity sha512-KGf6Dl9E6D01vAfkgkjL2sG+zqAjspAogILIpWstljWdG5ifyA75jihrnDEHaMCoQS0KxHvTdP1XYS/GS6BEyQ== - "@celo/bls12377js@0.1.1": version "0.1.1" resolved "https://registry.npmjs.org/@celo/bls12377js/-/bls12377js-0.1.1.tgz" @@ -974,59 +909,51 @@ "@stablelib/blake2xs" "0.10.4" big-integer "^1.6.44" -"@celo/connect@1.5.2": - version "1.5.2" - resolved "https://registry.npmjs.org/@celo/connect/-/connect-1.5.2.tgz" - integrity sha512-IHsvYp1HizIPfPPeIHyvsmJytIf7HNtNWo9CqCbsqfNfmw53q6dFJu2p5X0qz/fUnR5840cUga8cEyuYZTfp+w== - dependencies: - "@celo/utils" "1.5.2" - "@types/debug" "^4.1.5" - "@types/utf8" "^2.1.6" - bignumber.js "^9.0.0" - debug "^4.1.1" - utf8 "3.0.0" - -"@celo/contractkit@1.5.2": - version "1.5.2" - resolved "https://registry.npmjs.org/@celo/contractkit/-/contractkit-1.5.2.tgz" - integrity sha512-b0r5TlfYDEscxze1Ai2jyJayiVElA9jvEehMD6aOSNtVhfP8oirjFIIffRe0Wzw1MSDGkw+q1c4m0Yw5sEOlvA== +"@celo/identity@^5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@celo/identity/-/identity-5.0.4.tgz#77ff3c64d6a9d1e74aa75ec57b331f66504c9411" + integrity sha512-q4onYB78+sWJ5ii5zZjP6s6SA3DzCmxVEchg7ZWd8F94hqufPKGy1ld0tnaUmqlONJJB2QEgjp0c1KqCgmvf+Q== dependencies: - "@celo/base" "1.5.2" - "@celo/connect" "1.5.2" - "@celo/utils" "1.5.2" - "@celo/wallet-local" "1.5.2" + "@celo/base" "5.0.4" + "@celo/contractkit" "5.0.4" + "@celo/phone-number-privacy-common" "^3.0.3" + "@celo/utils" "5.0.4" "@types/debug" "^4.1.5" bignumber.js "^9.0.0" - cross-fetch "^3.0.6" + blind-threshold-bls "npm:@celo/blind-threshold-bls@1.0.0-beta" + cross-fetch "3.0.6" debug "^4.1.1" + elliptic "^6.5.4" + ethereum-cryptography "1.2.0" fp-ts "2.1.1" io-ts "2.0.1" - semver "^7.3.5" - web3 "1.3.6" -"@celo/phone-number-privacy-common@1.0.39": - version "1.0.39" - resolved "https://registry.npmjs.org/@celo/phone-number-privacy-common/-/phone-number-privacy-common-1.0.39.tgz" - integrity sha512-0sbeuoYCN2ZQYO1CryR0Hf9HhOQKuIDZraWFMpUlwrUKk5qKmSMlV16xobG4VL5qUpXHgIRjKPfmcaf0rkrn8A== - dependencies: - "@celo/base" "1.5.2" - "@celo/contractkit" "1.5.2" - "@celo/utils" "1.5.2" +"@celo/phone-number-privacy-common@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@celo/phone-number-privacy-common/-/phone-number-privacy-common-3.0.3.tgz#6e79909a222b75d7e40d8e658572ece16f2c90e9" + integrity sha512-BZQ4O/9p03y0rvgofcfVMPb1Ol5UtLAB7PbHvMpANI4v1h0Qk0K5ftPOVGLE4XwKf5m2vm/xThme/WQSWvZayw== + dependencies: + "@celo/base" "^5.0.2" + "@celo/contractkit" "^5.0.2" + "@celo/phone-utils" "^5.0.2" + "@celo/utils" "^5.0.2" + "@opentelemetry/api" "^1.4.1" + "@opentelemetry/auto-instrumentations-node" "^0.38.0" + "@opentelemetry/propagator-ot-trace" "^0.27.0" + "@opentelemetry/sdk-metrics" "^1.15.1" + "@opentelemetry/sdk-node" "^0.41.1" + "@opentelemetry/sdk-trace-web" "^1.15.1" + "@opentelemetry/semantic-conventions" "^1.15.1" + "@types/bunyan" "1.8.8" bignumber.js "^9.0.0" - blind-threshold-bls "https://github.com/celo-org/blind-threshold-bls-wasm#e1e2f8a" - btoa "1.2.1" bunyan "1.8.12" bunyan-debug-stream "2.0.0" bunyan-gke-stackdriver "0.1.2" dotenv "^8.2.0" elliptic "^6.5.4" + io-ts "2.0.1" is-base64 "^1.1.0" -"@celo/poprf@^0.1.9": - version "0.1.9" - resolved "https://registry.npmjs.org/@celo/poprf/-/poprf-0.1.9.tgz" - integrity sha512-+993EA/W+TBCZyY5G0B2EVdXnPX6t2AldgRAIMaT9WIqTwZKi/TcdJDUQl8mj7HEHMPHlpgCBOVgaHkUcwo/5A== - "@celo/typechain-target-web3-v1-celo@0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@celo/typechain-target-web3-v1-celo/-/typechain-target-web3-v1-celo-0.2.0.tgz#2560b470e348d2628debe899885724ce5b218bc3" @@ -1035,66 +962,6 @@ lodash "~4.17.19" typechain "2.0.0" -"@celo/utils@1.5.2": - version "1.5.2" - resolved "https://registry.npmjs.org/@celo/utils/-/utils-1.5.2.tgz" - integrity sha512-JyKjuVMbdkyFOb1TpQw6zqamPQWYg7I9hOnva3MeIcQ3ZrJIaNHx0/I+JXFjuu3YYBc1mG8nXp2uPJJTGrwzCQ== - dependencies: - "@celo/base" "1.5.2" - "@types/country-data" "^0.0.0" - "@types/elliptic" "^6.4.9" - "@types/ethereumjs-util" "^5.2.0" - "@types/google-libphonenumber" "^7.4.17" - "@types/lodash" "^4.14.170" - "@types/node" "^10.12.18" - "@types/randombytes" "^2.0.0" - bigi "^1.1.0" - bignumber.js "^9.0.0" - bip32 "2.0.5" - bip39 "https://github.com/bitcoinjs/bip39#d8ea080a18b40f301d4e2219a2991cd2417e83c2" - bls12377js "https://github.com/celo-org/bls12377js#cb38a4cfb643c778619d79b20ca3e5283a2122a6" - bn.js "4.11.8" - buffer-reverse "^1.0.1" - country-data "^0.0.31" - crypto-js "^3.1.9-1" - elliptic "^6.5.4" - ethereumjs-util "^5.2.0" - fp-ts "2.1.1" - google-libphonenumber "^3.2.15" - io-ts "2.0.1" - keccak256 "^1.0.0" - lodash "^4.17.21" - numeral "^2.0.6" - web3-eth-abi "1.3.6" - web3-utils "1.3.6" - -"@celo/wallet-base@1.5.2": - version "1.5.2" - resolved "https://registry.npmjs.org/@celo/wallet-base/-/wallet-base-1.5.2.tgz" - integrity sha512-NYJu7OtSRFpGcvSMl2Wc8zN32S6oTkAzKqhH7rXisQ0I2q4yNwCzoquzPVYB0G2UVUFKuuxgsA5V+Zda/LQCyw== - dependencies: - "@celo/base" "1.5.2" - "@celo/connect" "1.5.2" - "@celo/utils" "1.5.2" - "@types/debug" "^4.1.5" - "@types/ethereumjs-util" "^5.2.0" - bignumber.js "^9.0.0" - debug "^4.1.1" - eth-lib "^0.2.8" - ethereumjs-util "^5.2.0" - -"@celo/wallet-local@1.5.2": - version "1.5.2" - resolved "https://registry.npmjs.org/@celo/wallet-local/-/wallet-local-1.5.2.tgz" - integrity sha512-Aas4SwqQc8ap0OFAOZc+jBR4cXr20V9AReHNEI8Y93R3g1+RlSEJ1Zmsu4vN+Rriz58YqgMnr+pihorw8QydFQ== - dependencies: - "@celo/connect" "1.5.2" - "@celo/utils" "1.5.2" - "@celo/wallet-base" "1.5.2" - "@types/ethereumjs-util" "^5.2.0" - eth-lib "^0.2.8" - ethereumjs-util "^5.2.0" - "@chainsafe/as-sha256@^0.3.1": version "0.3.1" resolved "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz" @@ -1116,11 +983,6 @@ "@chainsafe/persistent-merkle-tree" "^0.4.2" case "^1.6.3" -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" @@ -1128,15 +990,6 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@dabh/diagnostics@^2.0.2": - version "2.0.3" - resolved "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz" - integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== - dependencies: - colorspace "1.1.x" - enabled "2.0.x" - kuler "^2.0.0" - "@ensdomains/address-encoder@^0.1.7": version "0.1.9" resolved "https://registry.npmjs.org/@ensdomains/address-encoder/-/address-encoder-0.1.9.tgz" @@ -1369,21 +1222,6 @@ mcl-wasm "^0.7.1" rustbn.js "~0.2.0" -"@ethersproject/abi@5.0.7": - version "5.0.7" - resolved "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.0.7.tgz" - integrity sha512-Cqktk+hSIckwP/W8O47Eef60VwmoSC/L3lY0+dIBhQPCNn9E4V7rwmm2aFrNRRDJfFlGuZ1khkQUOc3oBX+niw== - dependencies: - "@ethersproject/address" "^5.0.4" - "@ethersproject/bignumber" "^5.0.7" - "@ethersproject/bytes" "^5.0.4" - "@ethersproject/constants" "^5.0.4" - "@ethersproject/hash" "^5.0.4" - "@ethersproject/keccak256" "^5.0.3" - "@ethersproject/logger" "^5.0.5" - "@ethersproject/properties" "^5.0.3" - "@ethersproject/strings" "^5.0.4" - "@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.0-beta.146", "@ethersproject/abi@^5.6.3", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz" @@ -1423,7 +1261,7 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/address@5.7.0", "@ethersproject/address@^5.0.4", "@ethersproject/address@^5.7.0": +"@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz" integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== @@ -1449,7 +1287,7 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.0.7", "@ethersproject/bignumber@^5.7.0": +"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz" integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== @@ -1458,14 +1296,14 @@ "@ethersproject/logger" "^5.7.0" bn.js "^5.2.1" -"@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.0.4", "@ethersproject/bytes@^5.7.0": +"@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz" integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.0.4", "@ethersproject/constants@^5.7.0": +"@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz" integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== @@ -1488,7 +1326,7 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/transactions" "^5.7.0" -"@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.0.4", "@ethersproject/hash@^5.7.0": +"@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz" integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== @@ -1540,7 +1378,7 @@ aes-js "3.0.0" scrypt-js "3.0.1" -"@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.0.3", "@ethersproject/keccak256@^5.7.0": +"@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz" integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== @@ -1548,7 +1386,7 @@ "@ethersproject/bytes" "^5.7.0" js-sha3 "0.8.0" -"@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.0.5", "@ethersproject/logger@^5.7.0": +"@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz" integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== @@ -1568,7 +1406,7 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/sha2" "^5.7.0" -"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.0.3", "@ethersproject/properties@^5.7.0": +"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz" integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== @@ -1650,7 +1488,7 @@ "@ethersproject/sha2" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.0.4", "@ethersproject/strings@^5.7.0": +"@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz" integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== @@ -1659,7 +1497,7 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.0.0-beta.135", "@ethersproject/transactions@^5.6.2", "@ethersproject/transactions@^5.7.0": +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.6.2", "@ethersproject/transactions@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz" integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== @@ -1726,153 +1564,6 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@fastify/busboy@^1.2.1": - version "1.2.1" - resolved "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz" - integrity sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q== - dependencies: - text-decoding "^1.0.0" - -"@firebase/app-types@0.6.3": - version "0.6.3" - resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz" - integrity sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw== - -"@firebase/app-types@0.7.0": - version "0.7.0" - resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz" - integrity sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg== - -"@firebase/app-types@0.9.0": - version "0.9.0" - resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz" - integrity sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q== - -"@firebase/auth-interop-types@0.1.6": - version "0.1.6" - resolved "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz" - integrity sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g== - -"@firebase/auth-interop-types@0.2.1": - version "0.2.1" - resolved "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz" - integrity sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg== - -"@firebase/component@0.5.13": - version "0.5.13" - resolved "https://registry.npmjs.org/@firebase/component/-/component-0.5.13.tgz" - integrity sha512-hxhJtpD8Ppf/VU2Rlos6KFCEV77TGIGD5bJlkPK1+B/WUe0mC6dTjW7KhZtXTc+qRBp9nFHWcsIORnT8liHP9w== - dependencies: - "@firebase/util" "1.5.2" - tslib "^2.1.0" - -"@firebase/component@0.6.4": - version "0.6.4" - resolved "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz" - integrity sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA== - dependencies: - "@firebase/util" "1.9.3" - tslib "^2.1.0" - -"@firebase/database-compat@^0.1.1": - version "0.1.8" - resolved "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.8.tgz" - integrity sha512-dhXr5CSieBuKNdU96HgeewMQCT9EgOIkfF1GNy+iRrdl7BWLxmlKuvLfK319rmIytSs/vnCzcD9uqyxTeU/A3A== - dependencies: - "@firebase/component" "0.5.13" - "@firebase/database" "0.12.8" - "@firebase/database-types" "0.9.7" - "@firebase/logger" "0.3.2" - "@firebase/util" "1.5.2" - tslib "^2.1.0" - -"@firebase/database-compat@^0.3.4": - version "0.3.4" - resolved "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz" - integrity sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg== - dependencies: - "@firebase/component" "0.6.4" - "@firebase/database" "0.14.4" - "@firebase/database-types" "0.10.4" - "@firebase/logger" "0.4.0" - "@firebase/util" "1.9.3" - tslib "^2.1.0" - -"@firebase/database-types@0.10.4", "@firebase/database-types@^0.10.4": - version "0.10.4" - resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz" - integrity sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ== - dependencies: - "@firebase/app-types" "0.9.0" - "@firebase/util" "1.9.3" - -"@firebase/database-types@0.9.7": - version "0.9.7" - resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.7.tgz" - integrity sha512-EFhgL89Fz6DY3kkB8TzdHvdu8XaqqvzcF2DLVOXEnQ3Ms7L755p5EO42LfxXoJqb9jKFvgLpFmKicyJG25WFWw== - dependencies: - "@firebase/app-types" "0.7.0" - "@firebase/util" "1.5.2" - -"@firebase/database-types@^0.7.2": - version "0.7.3" - resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz" - integrity sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A== - dependencies: - "@firebase/app-types" "0.6.3" - -"@firebase/database@0.12.8": - version "0.12.8" - resolved "https://registry.npmjs.org/@firebase/database/-/database-0.12.8.tgz" - integrity sha512-JBQVfFLzfhxlQbl4OU6ov9fdsddkytBQdtSSR49cz48homj38ccltAhK6seum+BI7f28cV2LFHF9672lcN+qxA== - dependencies: - "@firebase/auth-interop-types" "0.1.6" - "@firebase/component" "0.5.13" - "@firebase/logger" "0.3.2" - "@firebase/util" "1.5.2" - faye-websocket "0.11.4" - tslib "^2.1.0" - -"@firebase/database@0.14.4": - version "0.14.4" - resolved "https://registry.npmjs.org/@firebase/database/-/database-0.14.4.tgz" - integrity sha512-+Ea/IKGwh42jwdjCyzTmeZeLM3oy1h0mFPsTy6OqCWzcu/KFqRAr5Tt1HRCOBlNOdbh84JPZC47WLU18n2VbxQ== - dependencies: - "@firebase/auth-interop-types" "0.2.1" - "@firebase/component" "0.6.4" - "@firebase/logger" "0.4.0" - "@firebase/util" "1.9.3" - faye-websocket "0.11.4" - tslib "^2.1.0" - -"@firebase/logger@0.3.2": - version "0.3.2" - resolved "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz" - integrity sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA== - dependencies: - tslib "^2.1.0" - -"@firebase/logger@0.4.0": - version "0.4.0" - resolved "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz" - integrity sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA== - dependencies: - tslib "^2.1.0" - -"@firebase/util@1.5.2": - version "1.5.2" - resolved "https://registry.npmjs.org/@firebase/util/-/util-1.5.2.tgz" - integrity sha512-YvBH2UxFcdWG2HdFnhxZptPl2eVFlpOyTH66iDo13JPEYraWzWToZ5AMTtkyRHVmu7sssUpQlU9igy1KET7TOw== - dependencies: - tslib "^2.1.0" - -"@firebase/util@1.9.3": - version "1.9.3" - resolved "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz" - integrity sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA== - dependencies: - tslib "^2.1.0" - "@ganache/console.log@0.2.0": version "0.2.0" resolved "https://registry.npmjs.org/@ganache/console.log/-/console.log-0.2.0.tgz" @@ -1900,7 +1591,7 @@ optionalDependencies: "@trufflesuite/bigint-buffer" "1.1.9" -"@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": +"@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== @@ -1922,26 +1613,6 @@ retry-request "^4.0.0" teeny-request "^3.11.3" -"@google-cloud/firestore@^4.5.0": - version "4.15.1" - resolved "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz" - integrity sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA== - dependencies: - fast-deep-equal "^3.1.1" - functional-red-black-tree "^1.0.1" - google-gax "^2.24.1" - protobufjs "^6.8.6" - -"@google-cloud/firestore@^6.6.0": - version "6.7.0" - resolved "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.7.0.tgz" - integrity sha512-bkH2jb5KkQSUa+NAvpip9HQ+rpYhi77IaqHovWuN07adVmvNXX08gPpvPWEzoXYa/wDjEVI7LiAtCWkJJEYTNg== - dependencies: - fast-deep-equal "^3.1.1" - functional-red-black-tree "^1.0.1" - google-gax "^3.5.7" - protobufjs "^7.0.0" - "@google-cloud/kms@~2.9.0": version "2.9.0" resolved "https://registry.npmjs.org/@google-cloud/kms/-/kms-2.9.0.tgz" @@ -1967,67 +1638,21 @@ split-array-stream "^2.0.0" stream-events "^1.0.4" -"@google-cloud/paginator@^3.0.6", "@google-cloud/paginator@^3.0.7": - version "3.0.7" - resolved "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz" - integrity sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ== - dependencies: - arrify "^2.0.0" - extend "^3.0.2" - -"@google-cloud/paginator@^4.0.0": - version "4.0.1" - resolved "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-4.0.1.tgz" - integrity sha512-6G1ui6bWhNyHjmbYwavdN7mpVPRBtyDg/bfqBTAlwr413On2TnFNfDxc9UhTJctkgoCDgQXEKiRPLPR9USlkbQ== - dependencies: - arrify "^2.0.0" - extend "^3.0.2" - "@google-cloud/precise-date@^0.1.0": version "0.1.0" resolved "https://registry.npmjs.org/@google-cloud/precise-date/-/precise-date-0.1.0.tgz" integrity sha512-nXt4AskYjmDLRIO+nquVVppjiLE5ficFRP3WF1JYtPnSRFRpuMusa1kysPsD/yOxt5NMmvlkUCkaFI4rHYeckQ== -"@google-cloud/precise-date@^2.0.0": - version "2.0.4" - resolved "https://registry.npmjs.org/@google-cloud/precise-date/-/precise-date-2.0.4.tgz" - integrity sha512-nOB+mZdevI/1Si0QAfxWfzzIqFdc7wrO+DYePFvgbOoMtvX+XfFTINNt7e9Zg66AbDbWCPRnikU+6f5LTm9Wyg== - -"@google-cloud/precise-date@^3.0.0": - version "3.0.1" - resolved "https://registry.npmjs.org/@google-cloud/precise-date/-/precise-date-3.0.1.tgz" - integrity sha512-crK2rgNFfvLoSgcKJY7ZBOLW91IimVNmPfi1CL+kMTf78pTJYd29XqEVedAeBu4DwCJc0EDIp1MpctLgoPq+Uw== - "@google-cloud/projectify@^0.3.0", "@google-cloud/projectify@^0.3.3": version "0.3.3" resolved "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.3.tgz" integrity sha512-7522YHQ4IhaafgSunsFF15nG0TGVmxgXidy9cITMe+256RgqfcrfWphiMufW+Ou4kqagW/u3yxwbzVEW3dk2Uw== -"@google-cloud/projectify@^2.0.0": - version "2.1.1" - resolved "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz" - integrity sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ== - -"@google-cloud/projectify@^3.0.0": - version "3.0.0" - resolved "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz" - integrity sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA== - "@google-cloud/promisify@^0.4.0": version "0.4.0" resolved "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.4.0.tgz" integrity sha512-4yAHDC52TEMCNcMzVC8WlqnKKKq+Ssi2lXoUg9zWWkZ6U6tq9ZBRYLHHCRdfU+EU9YJsVmivwGcKYCjRGjnf4Q== -"@google-cloud/promisify@^2.0.0": - version "2.0.4" - resolved "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz" - integrity sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA== - -"@google-cloud/promisify@^3.0.0": - version "3.0.1" - resolved "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz" - integrity sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA== - "@google-cloud/pubsub@^0.28.1": version "0.28.1" resolved "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-0.28.1.tgz" @@ -2052,49 +1677,6 @@ p-defer "^1.0.0" protobufjs "^6.8.1" -"@google-cloud/pubsub@^2.7.0": - version "2.19.4" - resolved "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-2.19.4.tgz" - integrity sha512-+aZxq6N5XGarQS3xGXjKSRFy4TB+3PMpI0CBmSrcC59g3TB5nmwps3pv/KkdLa0Cd+CPHDdfrEW1uSrGBMLICw== - dependencies: - "@google-cloud/paginator" "^3.0.6" - "@google-cloud/precise-date" "^2.0.0" - "@google-cloud/projectify" "^2.0.0" - "@google-cloud/promisify" "^2.0.0" - "@opentelemetry/api" "^1.0.0" - "@opentelemetry/semantic-conventions" "^1.0.0" - "@types/duplexify" "^3.6.0" - "@types/long" "^4.0.0" - arrify "^2.0.0" - extend "^3.0.2" - google-auth-library "^7.0.0" - google-gax "2.30.3" - is-stream-ended "^0.1.4" - lodash.snakecase "^4.1.1" - p-defer "^3.0.0" - -"@google-cloud/pubsub@^3.0.1": - version "3.7.3" - resolved "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-3.7.3.tgz" - integrity sha512-ZRDC4g7tpIJ8fkAp4MiU+tDfousM/q6pXK6ytFn0cbYEdNQuWOf4wqopNYMOUJ+AIjaTbgmNw77dStOKTc9Acg== - dependencies: - "@google-cloud/paginator" "^4.0.0" - "@google-cloud/precise-date" "^3.0.0" - "@google-cloud/projectify" "^3.0.0" - "@google-cloud/promisify" "^2.0.0" - "@opentelemetry/api" "^1.0.0" - "@opentelemetry/semantic-conventions" "~1.3.0" - "@types/duplexify" "^3.6.0" - "@types/long" "^4.0.0" - arrify "^2.0.0" - extend "^3.0.2" - google-auth-library "^8.0.2" - google-gax "^3.6.1" - heap-js "^2.2.0" - is-stream-ended "^0.1.4" - lodash.snakecase "^4.1.1" - p-defer "^3.0.0" - "@google-cloud/secret-manager@3.0.0": version "3.0.0" resolved "https://registry.npmjs.org/@google-cloud/secret-manager/-/secret-manager-3.0.0.tgz" @@ -2129,59 +1711,6 @@ through2 "^3.0.0" xdg-basedir "^3.0.0" -"@google-cloud/storage@^5.3.0": - version "5.20.5" - resolved "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.5.tgz" - integrity sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw== - dependencies: - "@google-cloud/paginator" "^3.0.7" - "@google-cloud/projectify" "^2.0.0" - "@google-cloud/promisify" "^2.0.0" - abort-controller "^3.0.0" - arrify "^2.0.0" - async-retry "^1.3.3" - compressible "^2.0.12" - configstore "^5.0.0" - duplexify "^4.0.0" - ent "^2.2.0" - extend "^3.0.2" - gaxios "^4.0.0" - google-auth-library "^7.14.1" - hash-stream-validation "^0.2.2" - mime "^3.0.0" - mime-types "^2.0.8" - p-limit "^3.0.1" - pumpify "^2.0.0" - retry-request "^4.2.2" - stream-events "^1.0.4" - teeny-request "^7.1.3" - uuid "^8.0.0" - xdg-basedir "^4.0.0" - -"@google-cloud/storage@^6.9.5": - version "6.12.0" - resolved "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.12.0.tgz" - integrity sha512-78nNAY7iiZ4O/BouWMWTD/oSF2YtYgYB3GZirn0To6eBOugjXVoK+GXgUXOl+HlqbAOyHxAVXOlsj3snfbQ1dw== - dependencies: - "@google-cloud/paginator" "^3.0.7" - "@google-cloud/projectify" "^3.0.0" - "@google-cloud/promisify" "^3.0.0" - abort-controller "^3.0.0" - async-retry "^1.3.3" - compressible "^2.0.12" - duplexify "^4.0.0" - ent "^2.2.0" - extend "^3.0.2" - fast-xml-parser "^4.2.2" - gaxios "^5.0.0" - google-auth-library "^8.0.1" - mime "^3.0.0" - mime-types "^2.0.8" - p-limit "^3.0.1" - retry-request "^5.0.0" - teeny-request "^8.0.0" - uuid "^8.0.0" - "@graphql-tools/batch-execute@8.5.1": version "8.5.1" resolved "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-8.5.1.tgz" @@ -2293,25 +1822,6 @@ "@grpc/proto-loader" "^0.7.0" "@types/node" ">=12.12.47" -"@grpc/grpc-js@~1.8.0": - version "1.8.21" - resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.21.tgz" - integrity sha512-KeyQeZpxeEBSqFVTi3q2K7PiPXmgBfECc4updA1ejCLjYmoAlvvM3ZMp5ztTDUCUQmoY3CpDxvchjO1+rFkoHg== - dependencies: - "@grpc/proto-loader" "^0.7.0" - "@types/node" ">=12.12.47" - -"@grpc/proto-loader@0.6.9": - version "0.6.9" - resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.9.tgz" - integrity sha512-UlcCS8VbsU9d3XTXGiEVFonN7hXk+oMXZtoHHG2oSA1/GcDP1q6OUgs20PzHDGizzyi8ufGSUDlk3O2NyY7leg== - dependencies: - "@types/long" "^4.0.1" - lodash.camelcase "^4.3.0" - long "^4.0.0" - protobufjs "^6.10.0" - yargs "^16.2.0" - "@grpc/proto-loader@^0.4.0": version "0.4.0" resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.4.0.tgz" @@ -2705,18 +2215,6 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@jsdevtools/ono@^7.1.3": - version "7.1.3" - resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz" - integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== - -"@jsdoc/salty@^0.2.1": - version "0.2.5" - resolved "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.5.tgz" - integrity sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw== - dependencies: - lodash "^4.17.21" - "@ledgerhq/cryptoassets@^5.53.0": version "5.53.0" resolved "https://registry.npmjs.org/@ledgerhq/cryptoassets/-/cryptoassets-5.53.0.tgz" @@ -3555,7 +3053,7 @@ npmlog "^6.0.2" write-file-atomic "^4.0.1" -"@mapbox/node-pre-gyp@^1.0.0", "@mapbox/node-pre-gyp@^1.0.4": +"@mapbox/node-pre-gyp@^1.0.4": version "1.0.11" resolved "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz" integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== @@ -3683,14 +3181,6 @@ treeverse "^2.0.0" walk-up-path "^1.0.0" -"@npmcli/fs@^1.0.0": - version "1.1.1" - resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz" - integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== - dependencies: - "@gar/promisify" "^1.0.1" - semver "^7.3.5" - "@npmcli/fs@^2.1.0": version "2.1.2" resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz" @@ -3749,14 +3239,6 @@ pacote "^13.0.3" semver "^7.3.5" -"@npmcli/move-file@^1.0.1": - version "1.1.2" - resolved "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz" - integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" - "@npmcli/move-file@^2.0.0": version "2.0.1" resolved "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz" @@ -4370,13 +3852,6 @@ dependencies: "@opentelemetry/semantic-conventions" "1.15.2" -"@opentelemetry/core@1.17.0": - version "1.17.0" - resolved "https://registry.npmjs.org/@opentelemetry/core/-/core-1.17.0.tgz" - integrity sha512-tfnl3h+UefCgx1aeN2xtrmr6BmdWGKXypk0pflQR0urFS40aE88trnkOMc2HTJZbMrqEEl4HsaBeFhwLVXsrJg== - dependencies: - "@opentelemetry/semantic-conventions" "1.17.0" - "@opentelemetry/exporter-jaeger@1.15.2": version "1.15.2" resolved "https://registry.npmjs.org/@opentelemetry/exporter-jaeger/-/exporter-jaeger-1.15.2.tgz" @@ -4387,16 +3862,6 @@ "@opentelemetry/semantic-conventions" "1.15.2" jaeger-client "^3.15.0" -"@opentelemetry/exporter-jaeger@^1.15.2": - version "1.17.0" - resolved "https://registry.npmjs.org/@opentelemetry/exporter-jaeger/-/exporter-jaeger-1.17.0.tgz" - integrity sha512-rWS5CQ+ns0NM3pmOAebaQdOmSnH6/7/P82EotaIq3zWrV5XRnKCRuILii457KnLqAI5zWjRTTEz2judEXNcCgg== - dependencies: - "@opentelemetry/core" "1.17.0" - "@opentelemetry/sdk-trace-base" "1.17.0" - "@opentelemetry/semantic-conventions" "1.17.0" - jaeger-client "^3.15.0" - "@opentelemetry/exporter-trace-otlp-grpc@0.41.2": version "0.41.2" resolved "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.41.2.tgz" @@ -4893,14 +4358,6 @@ "@opentelemetry/core" "1.15.2" "@opentelemetry/semantic-conventions" "1.15.2" -"@opentelemetry/resources@1.17.0": - version "1.17.0" - resolved "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.17.0.tgz" - integrity sha512-+u0ciVnj8lhuL/qGRBPeVYvk7fL+H/vOddfvmOeJaA1KC+5/3UED1c9KoZQlRsNT5Kw1FaK8LkY2NVLYfOVZQw== - dependencies: - "@opentelemetry/core" "1.17.0" - "@opentelemetry/semantic-conventions" "1.17.0" - "@opentelemetry/sdk-logs@0.41.2": version "0.41.2" resolved "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.41.2.tgz" @@ -4918,16 +4375,7 @@ "@opentelemetry/resources" "1.15.2" lodash.merge "^4.6.2" -"@opentelemetry/sdk-metrics@^1.15.2": - version "1.17.0" - resolved "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.17.0.tgz" - integrity sha512-HlWM27yGmYuwCoVRe3yg2PqKnIsq0kEF0HQgvkeDWz2NYkq9fFaSspR6kvjxUTbghAlZrabiqbgyKoYpYaXS3w== - dependencies: - "@opentelemetry/core" "1.17.0" - "@opentelemetry/resources" "1.17.0" - lodash.merge "^4.6.2" - -"@opentelemetry/sdk-node@^0.41.0", "@opentelemetry/sdk-node@^0.41.1", "@opentelemetry/sdk-node@^0.41.2": +"@opentelemetry/sdk-node@^0.41.0", "@opentelemetry/sdk-node@^0.41.1": version "0.41.2" resolved "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.41.2.tgz" integrity sha512-t3vaB5ajoXLtVFoL8TSoSgaVATmOyUfkIfBE4nvykm0dM2vQjMS/SUUelzR06eiPTbMPsr2UkevWhy2/oXy2vg== @@ -4956,15 +4404,6 @@ "@opentelemetry/resources" "1.15.2" "@opentelemetry/semantic-conventions" "1.15.2" -"@opentelemetry/sdk-trace-base@1.17.0", "@opentelemetry/sdk-trace-base@^1.17.0": - version "1.17.0" - resolved "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.17.0.tgz" - integrity sha512-2T5HA1/1iE36Q9eg6D4zYlC4Y4GcycI1J6NsHPKZY9oWfAxWsoYnRlkPfUqyY5XVtocCo/xHpnJvGNHwzT70oQ== - dependencies: - "@opentelemetry/core" "1.17.0" - "@opentelemetry/resources" "1.17.0" - "@opentelemetry/semantic-conventions" "1.17.0" - "@opentelemetry/sdk-trace-node@1.15.2": version "1.15.2" resolved "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.15.2.tgz" @@ -4986,35 +4425,16 @@ "@opentelemetry/sdk-trace-base" "1.15.2" "@opentelemetry/semantic-conventions" "1.15.2" -"@opentelemetry/sdk-trace-web@^1.15.2": - version "1.17.0" - resolved "https://registry.npmjs.org/@opentelemetry/sdk-trace-web/-/sdk-trace-web-1.17.0.tgz" - integrity sha512-vvE0zcco5tuOi16PsG6QkLOfEuzGAdzSLQPQgzRa6OGOo6ZeCpQ2JP+1U5a0pliAhmWYpVsW9u8T0qAYrHh3Bw== - dependencies: - "@opentelemetry/core" "1.17.0" - "@opentelemetry/sdk-trace-base" "1.17.0" - "@opentelemetry/semantic-conventions" "1.17.0" - "@opentelemetry/semantic-conventions@1.15.2", "@opentelemetry/semantic-conventions@^1.15.1": version "1.15.2" resolved "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.15.2.tgz" integrity sha512-CjbOKwk2s+3xPIMcd5UNYQzsf+v94RczbdNix9/kQh38WiQkM90sUOi3if8eyHFgiBjBjhwXrA7W3ydiSQP9mw== -"@opentelemetry/semantic-conventions@1.17.0", "@opentelemetry/semantic-conventions@^1.15.2": - version "1.17.0" - resolved "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.17.0.tgz" - integrity sha512-+fguCd2d8d2qruk0H0DsCEy2CTK3t0Tugg7MhZ/UQMvmewbZLNnJ6heSYyzIZWG5IPfAXzoj4f4F/qpM7l4VBA== - "@opentelemetry/semantic-conventions@^1.0.0": version "1.12.0" resolved "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.12.0.tgz" integrity sha512-hO+bdeGOlJwqowUBoZF5LyP3ORUFOP1G0GRv8N45W/cztXbT2ZEXaAzfokRS9Xc9FWmYrDj32mF6SzH6wuoIyA== -"@opentelemetry/semantic-conventions@~1.3.0": - version "1.3.1" - resolved "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.3.1.tgz" - integrity sha512-wU5J8rUoo32oSef/rFpOT1HIjLjAv3qIDHkw1QIhODV3OpAVHi5oVzlouozg9obUmZKtbZ0qUe/m7FP0y0yBzA== - "@opentelemetry/sql-common@^0.40.0": version "0.40.0" resolved "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.40.0.tgz" @@ -5043,11 +4463,6 @@ web3-eth-contract "1.2.2" web3-utils "1.2.2" -"@panva/asn1.js@^1.0.0": - version "1.0.0" - resolved "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz" - integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== - "@parcel/watcher@2.0.4": version "2.0.4" resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz" @@ -5061,27 +4476,6 @@ resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@pnpm/config.env-replace@^1.1.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz" - integrity sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w== - -"@pnpm/network.ca-file@^1.0.1": - version "1.0.2" - resolved "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz" - integrity sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA== - dependencies: - graceful-fs "4.2.10" - -"@pnpm/npm-conf@^2.1.0": - version "2.2.2" - resolved "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz" - integrity sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA== - dependencies: - "@pnpm/config.env-replace" "^1.1.0" - "@pnpm/network.ca-file" "^1.0.1" - config-chain "^1.1.11" - "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz" @@ -5410,11 +4804,6 @@ resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== -"@tootallnate/quickjs-emscripten@^0.23.0": - version "0.23.0" - resolved "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz" - integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA== - "@truffle/abi-utils@^0.3.0", "@truffle/abi-utils@^0.3.10", "@truffle/abi-utils@^0.3.9": version "0.3.10" resolved "https://registry.npmjs.org/@truffle/abi-utils/-/abi-utils-0.3.10.tgz" @@ -6062,13 +5451,6 @@ dependencies: "@types/node" "*" -"@types/archiver@^5.1.0": - version "5.3.2" - resolved "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.2.tgz" - integrity sha512-IctHreBuWE5dvBDz/0WeKtyVKVRs4h75IblxOACL92wU66v+HGAfEYAOyXkOFphvRJMhuXdI9huDXpX0FC6lCw== - dependencies: - "@types/readdir-glob" "*" - "@types/asn1js@^0.0.2": version "0.0.2" resolved "https://registry.npmjs.org/@types/asn1js/-/asn1js-0.0.2.tgz" @@ -6114,7 +5496,7 @@ dependencies: "@babel/types" "^7.3.0" -"@types/bn.js@*", "@types/bn.js@4.11.6", "@types/bn.js@^4.11.0", "@types/bn.js@^4.11.3", "@types/bn.js@^4.11.4", "@types/bn.js@^4.11.5", "@types/bn.js@^5.1.0", "@types/bn.js@^5.1.1": +"@types/bn.js@*", "@types/bn.js@4.11.6", "@types/bn.js@^4.11.0", "@types/bn.js@^4.11.3", "@types/bn.js@^4.11.4", "@types/bn.js@^5.1.0", "@types/bn.js@^5.1.1": version "4.11.6" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg== @@ -6129,7 +5511,7 @@ "@types/connect" "*" "@types/node" "*" -"@types/bunyan@1.8.8", "@types/bunyan@^1.8.8": +"@types/bunyan@1.8.8": version "1.8.8" resolved "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.8.tgz" integrity sha512-Cblq+Yydg3u+sGiz2mjHjC5MPmdjY+No4qvHrF+BUhblsmSfMvsHLbOG62tPbonsqBj6sbWv1LHcsoe5Jw+/Ow== @@ -6207,11 +5589,6 @@ resolved "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.5.tgz" integrity sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA== -"@types/cookiejar@*": - version "2.1.2" - resolved "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz" - integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog== - "@types/cookies@*": version "0.7.7" resolved "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.7.tgz" @@ -6227,13 +5604,6 @@ resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== -"@types/cors@^2.8.5": - version "2.8.14" - resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz" - integrity sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ== - dependencies: - "@types/node" "*" - "@types/country-data@^0.0.0": version "0.0.0" resolved "https://registry.npmjs.org/@types/country-data/-/country-data-0.0.0.tgz" @@ -6260,7 +5630,7 @@ dependencies: "@types/node" "*" -"@types/elliptic@^6.4.12", "@types/elliptic@^6.4.9": +"@types/elliptic@^6.4.9": version "6.4.14" resolved "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.14.tgz" integrity sha512-z4OBcDAU0GVwDTuwJzQCiL6188QvZMkvoERgcVjq0/mPM8jCfdwZ3x5zQEVoL9WCAru3aG5wl3Z5Ww5wBWn7ZQ== @@ -6274,24 +5644,6 @@ dependencies: bignumber.js "7.2.1" -"@types/ethereumjs-util@^5.2.0": - version "5.2.0" - resolved "https://registry.npmjs.org/@types/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz" - integrity sha512-qwQgQqXXTRv2h2AlJef+tMEszLFkCB9dWnrJYIdAwqjubERXEc/geB+S3apRw0yQyTVnsBf8r6BhlrE8vx+3WQ== - dependencies: - "@types/bn.js" "*" - "@types/node" "*" - -"@types/express-serve-static-core@*": - version "4.17.36" - resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz" - integrity sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - "@types/express-serve-static-core@4.17.31": version "4.17.31" resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz" @@ -6320,7 +5672,7 @@ "@types/qs" "*" "@types/range-parser" "*" -"@types/express@*", "@types/express@4.17.17", "@types/express@^4.17.14", "@types/express@^4.17.6": +"@types/express@*", "@types/express@4.17.17": version "4.17.17" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz" integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== @@ -6340,15 +5692,6 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/express@4.17.3": - version "4.17.3" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz" - integrity sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "*" - "@types/serve-static" "*" - "@types/fetch-mock@^7.3.5": version "7.3.5" resolved "https://registry.npmjs.org/@types/fetch-mock/-/fetch-mock-7.3.5.tgz" @@ -6384,11 +5727,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/google-libphonenumber@^7.4.17": - version "7.4.26" - resolved "https://registry.npmjs.org/@types/google-libphonenumber/-/google-libphonenumber-7.4.26.tgz" - integrity sha512-VkVHbNOMW3TFmXNvdShXjp6LBxaU5N88KotDb6ypkiSuwUyxNK4SM78EBNBh/0RjV0fiIbPcDzEVhDGSemHExw== - "@types/google-libphonenumber@^7.4.23": version "7.4.23" resolved "https://registry.npmjs.org/@types/google-libphonenumber/-/google-libphonenumber-7.4.23.tgz" @@ -6476,11 +5814,6 @@ dependencies: "@types/node" "*" -"@types/is-base64@^1.1.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/is-base64/-/is-base64-1.1.1.tgz#a17d2b0075f637f80f9ab5f76f0071a65f6965d4" - integrity sha512-JgnGhP+MeSHEQmvxcobcwPEP4Ew56voiq9/0hmP/41lyQ/3gBw/ZCIRy2v+QkEOdeCl58lRcrf6+Y6WMlJGETA== - "@types/isomorphic-fetch@0.0.31": version "0.0.31" resolved "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.31.tgz" @@ -6513,30 +5846,11 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/json-schema@^7.0.6": - version "7.0.12" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz" - integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== - "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/jsonwebtoken@^8.5.9": - version "8.5.9" - resolved "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz" - integrity sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg== - dependencies: - "@types/node" "*" - -"@types/jsonwebtoken@^9.0.0": - version "9.0.2" - resolved "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz" - integrity sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q== - dependencies: - "@types/node" "*" - "@types/keygrip@*": version "1.0.2" resolved "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz" @@ -6615,15 +5929,10 @@ "@types/abstract-leveldown" "*" "@types/node" "*" -"@types/linkify-it@*": - version "3.0.3" - resolved "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.3.tgz" - integrity sha512-pTjcqY9E4nOI55Wgpz7eiI8+LzdYnw3qxXCfHyBDdPbYvbyLgWLJGh8EdPvqawwMK1Uo1794AUkkR38Fr0g+2g== - -"@types/lodash@^4.14.104", "@types/lodash@^4.14.170": - version "4.14.198" - resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.198.tgz" - integrity sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg== +"@types/lodash@^4.14.199": + version "4.14.199" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.199.tgz#c3edb5650149d847a277a8961a7ad360c474e9bf" + integrity sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg== "@types/long@^3.0.0": version "3.0.32" @@ -6640,19 +5949,6 @@ resolved "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz" integrity sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw== -"@types/luxon@~3.3.0": - version "3.3.2" - resolved "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.2.tgz" - integrity sha512-l5cpE57br4BIjK+9BSkFBOsWtwv6J9bJpC7gdXIzZyI0vuKvNTk0wZZrkQxMGsUAuGW9+WMNWF2IJMD7br2yeQ== - -"@types/markdown-it@^12.2.3": - version "12.2.3" - resolved "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz" - integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ== - dependencies: - "@types/linkify-it" "*" - "@types/mdurl" "*" - "@types/mathjs@^4.4.1": version "4.4.5" resolved "https://registry.yarnpkg.com/@types/mathjs/-/mathjs-4.4.5.tgz#b28d46919c68b93bcabf0551729624b302af9b4b" @@ -6660,11 +5956,6 @@ dependencies: decimal.js "^10.0.0" -"@types/mdurl@*": - version "1.0.2" - resolved "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz" - integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== - "@types/memcached@^2.2.6": version "2.2.7" resolved "https://registry.npmjs.org/@types/memcached/-/memcached-2.2.7.tgz" @@ -6756,11 +6047,6 @@ resolved "https://registry.npmjs.org/@types/node/-/node-18.7.16.tgz" integrity sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg== -"@types/node@10.12.18": - version "10.12.18" - resolved "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz" - integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ== - "@types/node@11.11.6": version "11.11.6" resolved "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz" @@ -6771,7 +6057,7 @@ resolved "https://registry.npmjs.org/@types/node/-/node-12.12.54.tgz" integrity sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w== -"@types/node@18.15.13", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@^18.7.16": +"@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@^18.7.16": version "18.15.13" resolved "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz" integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== @@ -6781,7 +6067,7 @@ resolved "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== -"@types/node@^12.11.7", "@types/node@^12.12.17", "@types/node@^12.12.6", "@types/node@^12.6.1": +"@types/node@^12.12.6", "@types/node@^12.6.1": version "12.20.55" resolved "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== @@ -6923,7 +6209,7 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/readable-stream@^2.3.13", "@types/readable-stream@^2.3.5": +"@types/readable-stream@^2.3.13": version "2.3.15" resolved "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz" integrity sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ== @@ -6931,13 +6217,6 @@ "@types/node" "*" safe-buffer "~5.1.1" -"@types/readdir-glob@*": - version "1.1.1" - resolved "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.1.tgz" - integrity sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ== - dependencies: - "@types/node" "*" - "@types/request@^2.48.1": version "2.48.8" resolved "https://registry.npmjs.org/@types/request/-/request-2.48.8.tgz" @@ -6967,7 +6246,7 @@ resolved "https://registry.npmjs.org/@types/revalidator/-/revalidator-0.3.9.tgz" integrity sha512-oE8dY0aO6mmunrxZY1IfS23kpiPK5qqWfEkZzApQ1KegMRtc7LFx0wU+gGxDcIAvBTmO42x4vH10E++HVIs3gg== -"@types/rimraf@3.0.2", "@types/rimraf@^3.0.2": +"@types/rimraf@3.0.2": version "3.0.2" resolved "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz" integrity sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ== @@ -7040,21 +6319,6 @@ resolved "https://registry.npmjs.org/@types/string-hash/-/string-hash-1.1.1.tgz" integrity sha512-ijt3zdHi2DmZxQpQTmozXszzDo78V4R3EdvX0jFMfnMH2ZzQSmCbaWOMPGXFUYSzSIdStv78HDjg32m5dxc+tA== -"@types/superagent@*": - version "4.1.18" - resolved "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.18.tgz" - integrity sha512-LOWgpacIV8GHhrsQU+QMZuomfqXiqzz3ILLkCtKx3Us6AmomFViuzKT9D693QTKgyut2oCytMG8/efOop+DB+w== - dependencies: - "@types/cookiejar" "*" - "@types/node" "*" - -"@types/supertest@^2.0.12": - version "2.0.12" - resolved "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.12.tgz" - integrity sha512-X3HPWTwXRerBZS7Mo1k6vMVR1Z6zmJcDVn5O/31whe0tnjE4te6ZJSJGq1RiqHPjzPdMTfjCFogDJmwng9xHaQ== - dependencies: - "@types/superagent" "*" - "@types/tar-fs@*": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/tar-fs/-/tar-fs-2.0.2.tgz#d10b844cc1fcfa87de990a7cec350ee3d168c48b" @@ -7108,21 +6372,11 @@ resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz" integrity sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg== -"@types/triple-beam@^1.3.2": - version "1.3.3" - resolved "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.3.tgz" - integrity sha512-6tOUG+nVHn0cJbVp25JFayS5UE6+xlbcNF9Lo9mU7U0zk3zeUShZied4YEQZjy1JBF043FSkdXw8YkUJuVtB5g== - "@types/utf8@^2.1.6": version "2.1.6" resolved "https://registry.npmjs.org/@types/utf8/-/utf8-2.1.6.tgz" integrity sha512-pRs2gYF5yoKYrgSaira0DJqVg2tFuF+Qjp838xS7K+mJyY2jJzjsrl6y17GbIa4uMRogMbxs+ghNCvKg6XyNrA== -"@types/uuid@^7.0.3": - version "7.0.6" - resolved "https://registry.npmjs.org/@types/uuid/-/uuid-7.0.6.tgz" - integrity sha512-U/wu4HTp6T2dUmKqDtOUKS9cYhawuf8txqKF3Jp1iMDG8fP5HtjSldcN0g4m+/h7XHU1to1/HDCT0qeeUiu0EA== - "@types/web3-provider-engine@^14.0.0": version "14.0.1" resolved "https://registry.npmjs.org/@types/web3-provider-engine/-/web3-provider-engine-14.0.1.tgz" @@ -7180,11 +6434,6 @@ resolved "https://registry.yarnpkg.com/@wagmi/chains/-/chains-1.6.0.tgz#eb992ad28dbaaab729b5bcab3e5b461e8a035656" integrity sha512-5FRlVxse5P4ZaHG3GTvxwVANSmYJas1eQrTBHhjxVtqXoorm0aLmCHbhmN8Xo1yu09PaWKlleEvfE98yH4AgIw== -"@xmldom/xmldom@^0.8.3": - version "0.8.10" - resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz" - integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw== - "@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz" @@ -7205,7 +6454,7 @@ dependencies: argparse "^2.0.1" -JSONStream@^1.0.4, JSONStream@^1.2.1: +JSONStream@^1.0.4: version "1.3.5" resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== @@ -7331,7 +6580,7 @@ abstract-leveldown@~6.2.1, abstract-leveldown@~6.2.3: level-supports "~1.0.0" xtend "~4.0.0" -accepts@^1.3.5, accepts@~1.3.5, accepts@~1.3.8: +accepts@^1.3.5, accepts@~1.3.8: version "1.3.8" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -7344,12 +6593,12 @@ acorn-import-assertions@^1.9.0: resolved "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz" integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== -acorn-jsx@^5.0.0, acorn-jsx@^5.3.2: +acorn-jsx@^5.0.0: version "5.3.2" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^8.1.1, acorn-walk@^8.2.0: +acorn-walk@^8.1.1: version "8.2.0" resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== @@ -7359,25 +6608,11 @@ acorn@^6.0.7: resolved "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== -acorn@^8.4.1, acorn@^8.7.0, acorn@^8.8.2, acorn@^8.9.0: +acorn@^8.4.1, acorn@^8.8.2: version "8.10.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== -adal-node@^0.2.2: - version "0.2.4" - resolved "https://registry.npmjs.org/adal-node/-/adal-node-0.2.4.tgz" - integrity sha512-zIcvbwQFKMUtKxxj8YMHeTT1o/TPXfVNsTXVgXD8sxwV6h4AFQgK77dRciGhuEF9/Sdm3UQPJVPc/6XxrccSeA== - dependencies: - "@xmldom/xmldom" "^0.8.3" - async "^2.6.3" - axios "^0.21.1" - date-utils "*" - jws "3.x.x" - underscore ">= 1.3.1" - uuid "^3.1.0" - xpath.js "~1.1.0" - add-stream@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz" @@ -7393,7 +6628,7 @@ aes-js@^3.1.1, aes-js@^3.1.2: resolved "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz" integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ== -agent-base@6, agent-base@^6.0.0, agent-base@^6.0.2: +agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== @@ -7407,14 +6642,7 @@ agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" -agent-base@^7.0.2, agent-base@^7.1.0: - version "7.1.0" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz" - integrity sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg== - dependencies: - debug "^4.3.4" - -agentkeepalive@^4.1.3, agentkeepalive@^4.2.1: +agentkeepalive@^4.2.1: version "4.5.0" resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz" integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== @@ -7429,14 +6657,14 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv-formats@^2.1.0, ajv-formats@^2.1.1: +ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz" integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== dependencies: ajv "^8.0.0" -ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.5, ajv@^6.12.6, ajv@^6.6.1, ajv@^6.9.1: +ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.5, ajv@^6.6.1, ajv@^6.9.1: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -7446,7 +6674,7 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.5, ajv@^6.12.6, aj json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.3.0, ajv@^8.6.3: +ajv@^8.0.0, ajv@^8.6.3: version "8.12.0" resolved "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== @@ -7468,13 +6696,6 @@ amdefine@>=0.0.4: resolved "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz" integrity sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg== -ansi-align@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz" - integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== - dependencies: - string-width "^4.1.0" - ansi-color@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/ansi-color/-/ansi-color-0.2.1.tgz" @@ -7507,14 +6728,7 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: dependencies: type-fest "^0.21.3" -ansi-escapes@^6.2.0: - version "6.2.0" - resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz" - integrity sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw== - dependencies: - type-fest "^3.0.0" - -ansi-regex@^2.0.0, ansi-regex@^2.1.1: +ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== @@ -7539,11 +6753,6 @@ ansi-regex@^6.0.1: resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz" integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" - integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== - ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" @@ -7723,51 +6932,6 @@ aproba@^1.0.3: resolved "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== -archiver-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz" - integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== - dependencies: - glob "^7.1.4" - graceful-fs "^4.2.0" - lazystream "^1.0.0" - lodash.defaults "^4.2.0" - lodash.difference "^4.5.0" - lodash.flatten "^4.4.0" - lodash.isplainobject "^4.0.6" - lodash.union "^4.6.0" - normalize-path "^3.0.0" - readable-stream "^2.0.0" - -archiver-utils@^3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz" - integrity sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw== - dependencies: - glob "^7.2.3" - graceful-fs "^4.2.0" - lazystream "^1.0.0" - lodash.defaults "^4.2.0" - lodash.difference "^4.5.0" - lodash.flatten "^4.4.0" - lodash.isplainobject "^4.0.6" - lodash.union "^4.6.0" - normalize-path "^3.0.0" - readable-stream "^3.6.0" - -archiver@^5.0.0: - version "5.3.2" - resolved "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz" - integrity sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw== - dependencies: - archiver-utils "^2.1.0" - async "^3.2.4" - buffer-crc32 "^0.2.1" - readable-stream "^3.6.0" - readdir-glob "^1.1.2" - tar-stream "^2.2.0" - zip-stream "^4.1.0" - are-we-there-yet@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz" @@ -7887,16 +7051,11 @@ array-differ@^3.0.0: resolved "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz" integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== -array-flatten@1.1.1, array-flatten@^1.0.0: +array-flatten@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== -array-flatten@3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz" - integrity sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA== - array-ify@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz" @@ -7945,17 +7104,6 @@ array.prototype.map@^1.0.1: es-array-method-boxes-properly "^1.0.0" is-string "^1.0.7" -array.prototype.map@^1.0.5: - version "1.0.6" - resolved "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.6.tgz" - integrity sha512-nK1psgF2cXqP3wSyCSq0Hc7zwNq3sfljQqaG27r/7a7ooNUnn5nGq6yYWyks9jMO5EoFQ0ax80hSg6oXSRNXaw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-array-method-boxes-properly "^1.0.0" - is-string "^1.0.7" - array.prototype.reduce@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz" @@ -8001,20 +7149,6 @@ arrify@^2.0.0, arrify@^2.0.1: resolved "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -as-array@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/as-array/-/as-array-1.0.0.tgz" - integrity sha512-yTEVeqmnVlLJV0j8IAz/mcMGbr88+yX9SqTxyFc1HJwmW8Zy347jEmWFIg34MRqCUS8CXRKy8a8B/9BaoYDW2w== - dependencies: - lodash.isarguments "2.4.x" - lodash.isobject "^2.4.1" - lodash.values "^2.4.1" - -as-array@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/as-array/-/as-array-2.0.0.tgz" - integrity sha512-1Sd1LrodN0XYxYeZcN1J4xYZvmvTwD5tDWaPUGPIzH1mFsmzsPnVtd2exWhecMjtZk/wYWjNZJiD3b1SLCeJqg== - asap@^2.0.0, asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -8067,13 +7201,6 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== -ast-types@^0.13.2, ast-types@^0.13.4: - version "0.13.4" - resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz" - integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== - dependencies: - tslib "^2.0.1" - astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz" @@ -8096,11 +7223,6 @@ async-limiter@~1.0.0: resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async-lock@1.3.2: - version "1.3.2" - resolved "https://registry.npmjs.org/async-lock/-/async-lock-1.3.2.tgz" - integrity sha512-phnXdS3RP7PPcmP6NWWzWMU0sLTeyvtZCxBPpZdkYE3seGLKSQZs9FrmVO/qwypq98FUtWWUEYxziLkdGk5nnA== - async-mutex@^0.2.6: version "0.2.6" resolved "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.6.tgz" @@ -8108,7 +7230,7 @@ async-mutex@^0.2.6: dependencies: tslib "^2.0.0" -async-retry@^1.2.1, async-retry@^1.3.3: +async-retry@^1.2.1: version "1.3.3" resolved "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz" integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== @@ -8127,7 +7249,7 @@ async@^2.0.1, async@^2.1.2, async@^2.4.0, async@^2.5.0, async@^2.6.1: dependencies: lodash "^4.17.14" -async@^2.6.3, async@^2.6.4: +async@^2.6.4: version "2.6.4" resolved "https://registry.npmjs.org/async/-/async-2.6.4.tgz" integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== @@ -8372,23 +7494,6 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -basic-auth-connect@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz" - integrity sha512-kiV+/DTgVro4aZifY/hwRwALBISViL5NP4aReaR2EVJEObpbUBHIkdJh/YpcoEiYt7nBodZ6U2ajZeZvSxUCCg== - -basic-auth@~2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz" - integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== - dependencies: - safe-buffer "5.1.2" - -basic-ftp@^5.0.2: - version "5.0.3" - resolved "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz" - integrity sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g== - bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" @@ -8424,7 +7529,7 @@ big-integer@1.6.36: resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.36.tgz" integrity sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg== -big-integer@^1.6.15, big-integer@^1.6.17, big-integer@^1.6.44: +big-integer@^1.6.15, big-integer@^1.6.44: version "1.6.51" resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== @@ -8466,39 +7571,13 @@ binary-extensions@^2.0.0: resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -binary@~0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz" - integrity sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg== - dependencies: - buffers "~0.1.1" - chainsaw "~0.1.0" - -bindings@^1.2.1, bindings@^1.3.0, bindings@^1.5.0: +bindings@^1.2.1, bindings@^1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== dependencies: file-uri-to-path "1.0.0" -bintrees@1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz" - integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw== - -bip32@2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/bip32/-/bip32-2.0.5.tgz" - integrity sha512-zVY4VvJV+b2fS0/dcap/5XLlpqtgwyN8oRkuGgAS1uLOeEp0Yo6Tw2yUTozTtlrMJO3G8n4g/KX/XGFHW6Pq3g== - dependencies: - "@types/node" "10.12.18" - bs58check "^2.1.1" - create-hash "^1.2.0" - create-hmac "^1.1.7" - tiny-secp256k1 "^1.1.3" - typeforce "^1.11.5" - wif "^2.0.6" - bip32@3.1.0, bip32@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/bip32/-/bip32-3.1.0.tgz" @@ -8535,13 +7614,6 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" -bl@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/bl/-/bl-3.0.1.tgz" - integrity sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ== - dependencies: - readable-stream "^3.0.1" - bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" @@ -8556,38 +7628,21 @@ blakejs@^1.1.0: resolved "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz" integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== -"blind-threshold-bls@https://github.com/celo-org/blind-threshold-bls-wasm#e1e2f8a", "blind-threshold-bls@npm:@celo/blind-threshold-bls@1.0.0-beta": +"blind-threshold-bls@npm:@celo/blind-threshold-bls@1.0.0-beta": version "1.0.0-beta" resolved "https://registry.yarnpkg.com/@celo/blind-threshold-bls/-/blind-threshold-bls-1.0.0-beta.tgz#6c46e55c3720d99929d6d34dd3770b1623a09900" integrity sha512-sk9XLvbv0M0TJKJPHPc8FkIRTfP/PiPHeyKXPBTMZBW8URL4pRix9IfcT98zT5sA7hvMDJwgw3p3tM/L6Z1iGw== -"bls12377js@https://github.com/celo-org/bls12377js#cb38a4cfb643c778619d79b20ca3e5283a2122a6": - version "0.1.0" - resolved "https://github.com/celo-org/bls12377js#cb38a4cfb643c778619d79b20ca3e5283a2122a6" - dependencies: - "@stablelib/blake2xs" "0.10.4" - "@types/node" "^12.11.7" - big-integer "^1.6.44" - chai "^4.2.0" - mocha "^6.2.2" - ts-node "^8.4.1" - typescript "^3.6.4" - bluebird@^2.9.33: version "2.11.0" resolved "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz" integrity sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ== -bluebird@^3.5.0, bluebird@^3.5.2, bluebird@^3.7.2: +bluebird@^3.5.0, bluebird@^3.5.2: version "3.7.2" resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bluebird@~3.4.1: - version "3.4.7" - resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz" - integrity sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA== - bn.js@4.11.6: version "4.11.6" resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz" @@ -8631,7 +7686,7 @@ body-parser@1.20.1: type-is "~1.6.18" unpipe "1.0.0" -body-parser@^1.16.0, body-parser@^1.18.3, body-parser@^1.19.0: +body-parser@^1.16.0, body-parser@^1.19.0: version "1.20.2" resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz" integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== @@ -8654,34 +7709,6 @@ boolbase@^1.0.0: resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -boxen@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz" - integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^5.3.1" - chalk "^3.0.0" - cli-boxes "^2.2.0" - string-width "^4.1.0" - term-size "^2.1.0" - type-fest "^0.8.1" - widest-line "^3.1.0" - -boxen@^5.0.0: - version "5.1.2" - resolved "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz" - integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^6.2.0" - chalk "^4.1.0" - cli-boxes "^2.2.1" - string-width "^4.2.2" - type-fest "^0.20.2" - widest-line "^3.1.0" - wrap-ansi "^7.0.0" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" @@ -8848,7 +7875,7 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" -btoa@1.2.1, btoa@^1.2.1: +btoa@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz" integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g== @@ -8866,7 +7893,7 @@ buffer-alloc@^1.2.0: buffer-alloc-unsafe "^1.1.0" buffer-fill "^1.0.0" -buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3: +buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== @@ -8886,11 +7913,6 @@ buffer-from@1.1.2, buffer-from@^1.0.0, buffer-from@^1.1.1: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer-indexof-polyfill@~1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz" - integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== - buffer-reverse@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/buffer-reverse/-/buffer-reverse-1.0.1.tgz" @@ -8943,11 +7965,6 @@ buffer@^5.0.5, buffer@^5.2.1, buffer@^5.4.3, buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" -buffers@~0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz" - integrity sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ== - bufferutil@4.0.5, bufferutil@^4.0.1: version "4.0.5" resolved "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz" @@ -9025,40 +8042,11 @@ bytebuffer@~5: dependencies: long "~3" -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - bytes@3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cacache@^15.2.0: - version "15.3.0" - resolved "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz" - integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== - dependencies: - "@npmcli/fs" "^1.0.0" - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^6.0.0" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.0.2" - unique-filename "^1.1.1" - cacache@^16.0.0, cacache@^16.0.6, cacache@^16.1.0: version "16.1.3" resolved "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz" @@ -9259,13 +8247,6 @@ catering@^2.0.0, catering@^2.1.0, catering@^2.1.1: resolved "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz" integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w== -catharsis@^0.9.0: - version "0.9.0" - resolved "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz" - integrity sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A== - dependencies: - lodash "^4.17.15" - caw@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/caw/-/caw-1.2.0.tgz" @@ -9324,7 +8305,7 @@ chai@^4.0.1, chai@^4.3.6: pathval "^1.1.1" type-detect "^4.0.5" -chai@^4.2.0, chai@^4.3.7: +chai@^4.3.7: version "4.3.8" resolved "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz" integrity sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ== @@ -9337,24 +8318,6 @@ chai@^4.2.0, chai@^4.3.7: pathval "^1.1.1" type-detect "^4.0.5" -chainsaw@~0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz" - integrity sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ== - dependencies: - traverse ">=0.3.0 <0.4" - -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" - integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" @@ -9380,11 +8343,6 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^5.2.0: - version "5.3.0" - resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== - change-case@3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/change-case/-/change-case-3.0.2.tgz" @@ -9562,13 +8520,6 @@ cjs-module-lexer@^1.2.2: resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== -cjson@^0.3.1: - version "0.3.3" - resolved "https://registry.npmjs.org/cjson/-/cjson-0.3.3.tgz" - integrity sha512-yKNcXi/Mvi5kb1uK0sahubYiyfUO2EUgOp4NcY9+8NX5Xmc+4yeNogZuLFkpLBBj7/QI9MjRUIuXrV9XOw5kVg== - dependencies: - json-parse-helpfulerror "^1.0.3" - class-is@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz" @@ -9607,23 +8558,6 @@ clean-stack@^3.0.0: dependencies: escape-string-regexp "4.0.0" -cli-boxes@^2.2.0, cli-boxes@^2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz" - integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== - -cli-color@^1.2.0: - version "1.4.0" - resolved "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz" - integrity sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w== - dependencies: - ansi-regex "^2.1.1" - d "1" - es5-ext "^0.10.46" - es6-iterator "^2.0.3" - memoizee "^0.4.14" - timers-ext "^0.1.5" - cli-cursor@3.1.0, cli-cursor@^3.0.0, cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz" @@ -9670,16 +8604,7 @@ cli-table3@^0.5.0: optionalDependencies: colors "^1.1.2" -cli-table3@^0.6.3: - version "0.6.3" - resolved "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz" - integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== - dependencies: - string-width "^4.2.0" - optionalDependencies: - "@colors/colors" "1.5.0" - -cli-table@0.3.11, cli-table@^0.3.1: +cli-table@^0.3.1: version "0.3.11" resolved "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz" integrity sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ== @@ -9917,7 +8842,7 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.9.0, color-convert@^1.9.3: +color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -9936,42 +8861,16 @@ color-name@1.1.3: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.6.0: - version "1.9.1" - resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - color-support@^1.1.2, color-support@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -color@^3.1.3: - version "3.2.1" - resolved "https://registry.npmjs.org/color/-/color-3.2.1.tgz" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - -colorette@2.0.19: - version "2.0.19" - resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz" - integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== - -colorette@^2.0.19: - version "2.0.20" - resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - colors@1.0.3, colors@1.0.x: version "1.0.3" resolved "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz" @@ -9982,14 +8881,6 @@ colors@1.4.0, colors@^1.0.3, colors@^1.1.2: resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -colorspace@1.1.x: - version "1.1.4" - resolved "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz" - integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== - dependencies: - color "^3.1.3" - text-hex "1.0.x" - colour@~0.7.1: version "0.7.1" resolved "https://registry.npmjs.org/colour/-/colour-0.7.1.tgz" @@ -10039,21 +8930,11 @@ commander@3.0.2, commander@^3.0.0: resolved "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz" integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== -commander@^10.0.0: - version "10.0.1" - resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - commander@^2.12.1, commander@^2.20.3, commander@^2.8.1: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^4.0.1: - version "4.1.1" - resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - commander@^8.1.0: version "8.3.0" resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz" @@ -10077,13 +8958,6 @@ compare-func@^2.0.0: array-ify "^1.0.0" dot-prop "^5.1.0" -compare-semver@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/compare-semver/-/compare-semver-1.1.0.tgz" - integrity sha512-AENcdfhxsMCzzl+QRdOwMQeA8tZBEEacAmA4pGPoyco27G9sIaM98WNYkcToC9O0wIx1vE+1ErmaM4t0/fXhMw== - dependencies: - semver "^5.0.1" - compare-versions@^3.5.1: version "3.6.0" resolved "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz" @@ -10094,41 +8968,18 @@ complex.js@2.0.11: resolved "https://registry.npmjs.org/complex.js/-/complex.js-2.0.11.tgz" integrity sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw== -component-emitter@^1.2.0, component-emitter@^1.2.1, component-emitter@^1.3.0: +component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== -compress-commons@^4.1.2: - version "4.1.2" - resolved "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz" - integrity sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg== - dependencies: - buffer-crc32 "^0.2.13" - crc32-stream "^4.0.2" - normalize-path "^3.0.0" - readable-stream "^3.6.0" - -compressible@^2.0.12, compressible@~2.0.16: +compressible@^2.0.12: version "2.0.18" resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz" integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== dependencies: mime-db ">= 1.43.0 < 2" -compression@^1.7.0: - version "1.7.4" - resolved "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -10170,7 +9021,7 @@ conf@^10.1.2: pkg-up "^3.1.0" semver "^7.3.5" -config-chain@^1.1.11, config-chain@^1.1.12: +config-chain@^1.1.12: version "1.1.13" resolved "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz" integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== @@ -10190,28 +9041,6 @@ configstore@^4.0.0: write-file-atomic "^2.0.0" xdg-basedir "^3.0.0" -configstore@^5.0.0, configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - -connect@^3.6.2, connect@^3.7.0: - version "3.7.0" - resolved "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz" - integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== - dependencies: - debug "2.6.9" - finalhandler "1.1.2" - parseurl "~1.3.3" - utils-merge "1.0.1" - console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" @@ -10348,7 +9177,7 @@ cookie@0.5.0: resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== -cookiejar@^2.1.1, cookiejar@^2.1.4: +cookiejar@^2.1.1: version "2.1.4" resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz" integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== @@ -10437,14 +9266,6 @@ crc-32@^1.2.0, crc-32@^1.2.2: resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz" integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== -crc32-stream@^4.0.2: - version "4.0.3" - resolved "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz" - integrity sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw== - dependencies: - crc-32 "^1.2.0" - readable-stream "^3.4.0" - create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz" @@ -10481,29 +9302,13 @@ create-require@^1.1.0: resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cron@^2.4.1: - version "2.4.3" - resolved "https://registry.npmjs.org/cron/-/cron-2.4.3.tgz" - integrity sha512-YBvExkQYF7w0PxyeFLRyr817YVDhGxaCi5/uRRMqa4aWD3IFKRd+uNbpW1VWMdqQy8PZ7CElc+accXJcauPKzQ== - dependencies: - "@types/luxon" "~3.3.0" - luxon "~3.3.0" - -cross-env@^5.1.3, cross-env@^5.1.6: +cross-env@^5.1.6: version "5.2.1" resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.1.tgz#b2c76c1ca7add66dc874d11798466094f551b34d" integrity sha512-1yHhtcfAd1r4nwQgknowuUNfIT9E8dOMMspC36g45dN+iD1blloi7xp8X/xAIDnjHWyt1uQ8PHk2fkNaym7soQ== dependencies: cross-spawn "^6.0.5" -cross-fetch@3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.4.tgz" - integrity sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw== - dependencies: - node-fetch "2.6.0" - whatwg-fetch "3.0.0" - cross-fetch@3.0.6: version "3.0.6" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz" @@ -10511,13 +9316,6 @@ cross-fetch@3.0.6: dependencies: node-fetch "2.6.1" -cross-fetch@^3.0.6, cross-fetch@^3.1.5: - version "3.1.8" - resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz" - integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== - dependencies: - node-fetch "^2.6.12" - cross-fetch@^3.1.4: version "3.1.5" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz" @@ -10525,6 +9323,13 @@ cross-fetch@^3.1.4: dependencies: node-fetch "2.6.7" +cross-fetch@^3.1.5: + version "3.1.8" + resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz" + integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== + dependencies: + node-fetch "^2.6.12" + cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz" @@ -10536,7 +9341,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -10580,7 +9385,7 @@ crypto-browserify@3.12.0: randombytes "^2.0.0" randomfill "^1.0.3" -crypto-js@^3.1.4, crypto-js@^3.1.9-1: +crypto-js@^3.1.4: version "3.3.0" resolved "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz" integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q== @@ -10590,11 +9395,6 @@ crypto-random-string@^1.0.0: resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz" integrity sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg== -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - css-select@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz" @@ -10621,11 +9421,6 @@ csstype@^3.0.2: resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== -csv-parse@^5.0.4: - version "5.5.0" - resolved "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.0.tgz" - integrity sha512-RxruSK3M4XgzcD7Trm2wEN+SJ26ChIb903+IWxNOcB5q4jT2Cs+hFr6QP39J05EohshRFEvyzEBoZ/466S2sbw== - csv-parser@^2.0.0: version "2.3.5" resolved "https://registry.npmjs.org/csv-parser/-/csv-parser-2.3.5.tgz" @@ -10634,13 +9429,6 @@ csv-parser@^2.0.0: minimist "^1.2.0" through2 "^3.0.1" -csv-streamify@^3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/csv-streamify/-/csv-streamify-3.0.4.tgz" - integrity sha512-IQkxN0zu0gym8/5CHrSyReeRewbw+aRDrMrGI5WmIY/LmEcNpAcPOyETBHREKgsWHeEQWEihiBmx5EcKAsKWZw== - dependencies: - through2 "2.0.1" - csv-stringify@^4.3.1: version "4.3.1" resolved "https://registry.npmjs.org/csv-stringify/-/csv-stringify-4.3.1.tgz" @@ -10678,16 +9466,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -data-uri-to-buffer@3: - version "3.0.1" - resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz" - integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== - -data-uri-to-buffer@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-5.0.1.tgz" - integrity sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg== - dataloader@2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/dataloader/-/dataloader-2.1.0.tgz" @@ -10698,11 +9476,6 @@ date-and-time@^0.6.3: resolved "https://registry.npmjs.org/date-and-time/-/date-and-time-0.6.3.tgz" integrity sha512-lcWy3AXDRJOD7MplwZMmNSRM//kZtJaLz4n6D1P5z9wEmZGBKhJRBIr1Xs9KNQJmdXPblvgffynYji4iylUTcA== -date-utils@*: - version "1.2.21" - resolved "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz" - integrity sha512-wJMBjqlwXR0Iv0wUo/lFbhSQ7MmG1hl36iuxuE91kW+5b5sWbase73manEqNH9sOLFAMG83B4ffNKq9/Iq0FVA== - dateformat@^3.0.0: version "3.0.3" resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz" @@ -10743,14 +9516,7 @@ debug@4, debug@4.3.4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, de dependencies: ms "2.1.2" -debug@4.3.1: - version "4.3.1" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - -debug@^3.0.1, debug@^3.1.0, debug@^3.2.7: +debug@^3.0.1, debug@^3.1.0: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -10898,12 +9664,7 @@ deep-extend@^0.6.0: resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-freeze@0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/deep-freeze/-/deep-freeze-0.0.1.tgz" - integrity sha512-Z+z8HiAvsGwmjqlphnHW5oz6yWlOwu6EQfFTjmeTWlDeda3FS2yv3jhq35TX/ewmsnqB+RX2IdsIOyjJCQN5tg== - -deep-is@^0.1.3, deep-is@~0.1.3: +deep-is@~0.1.3: 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== @@ -11002,25 +9763,6 @@ defined@~1.0.1: resolved "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz" integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q== -degenerator@^3.0.2: - version "3.0.4" - resolved "https://registry.npmjs.org/degenerator/-/degenerator-3.0.4.tgz" - integrity sha512-Z66uPeBfHZAHVmue3HPfyKu2Q0rC2cRxbTOsvmU/po5fvvcx27W4mIu9n0PUlQih4oUYvcG1BsbtVv8x7KDOSw== - dependencies: - ast-types "^0.13.2" - escodegen "^1.8.1" - esprima "^4.0.0" - vm2 "^3.9.17" - -degenerator@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz" - integrity sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ== - dependencies: - ast-types "^0.13.4" - escodegen "^2.1.0" - esprima "^4.0.1" - delay@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz" @@ -11041,12 +9783,7 @@ delimit-stream@0.1.0: resolved "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz" integrity sha512-a02fiQ7poS5CnjiJBAsjGLPp5EwVoGHNeu9sziBd9huppRfsAFIpv5zNLv0V1gbop53ilngAf5Kf331AwcoRBQ== -denque@^2.0.1: - version "2.1.0" - resolved "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz" - integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== - -depd@2.0.0, depd@^2.0.0, depd@~2.0.0: +depd@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -11069,7 +9806,7 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -destroy@1.2.0, destroy@^1.0.4: +destroy@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== @@ -11118,7 +9855,7 @@ detect-node@2.0.3: resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz" integrity sha512-64uDTOK+fKEa6XoSbkkDoeAX8Ep1XhwxwZtL1aw1En5p5UOK/ekJoFqd5BB1o+uOvF1iHVv6qDUxdOQ/VgWEQg== -dezalgo@^1.0.0, dezalgo@^1.0.4: +dezalgo@^1.0.0: version "1.0.4" resolved "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz" integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== @@ -11126,13 +9863,6 @@ dezalgo@^1.0.0, dezalgo@^1.0.4: asap "^2.0.0" wrappy "1" -dicer@^0.3.0: - version "0.3.1" - resolved "https://registry.npmjs.org/dicer/-/dicer-0.3.1.tgz" - integrity sha512-ObioMtXnmjYs3aRtpIJt9rgQSPCIhKVkFPip+E9GUDyWl8N435znUxK/JfNwGZJ2wnn5JKQ7Ly3vOK5Q5dylGA== - dependencies: - streamsearch "^1.1.0" - diff-sequences@^29.4.3: version "29.4.3" resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz" @@ -11261,7 +9991,7 @@ dot-prop@^4.1.0: dependencies: is-obj "^1.0.0" -dot-prop@^5.1.0, dot-prop@^5.2.0: +dot-prop@^5.1.0: version "5.3.0" resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz" integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== @@ -11290,11 +10020,6 @@ dotenv@8.2.0: resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== -dotenv@^6.1.0: - version "6.2.0" - resolved "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz" - integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w== - dotenv@^8.2.0: version "8.6.0" resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz" @@ -11333,13 +10058,6 @@ dtrace-provider@~0.8: dependencies: nan "^2.14.0" -duplexer2@~0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz" - integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA== - dependencies: - readable-stream "^2.0.2" - duplexer3@^0.1.4: version "0.1.5" resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz" @@ -11360,7 +10078,7 @@ duplexify@^3.5.0, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" -duplexify@^4.0.0, duplexify@^4.1.1: +duplexify@^4.0.0: version "4.1.2" resolved "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz" integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== @@ -11468,11 +10186,6 @@ emoji-regex@^9.2.2: resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -enabled@2.0.x: - version "2.0.0" - resolved "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz" - integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" @@ -11499,7 +10212,7 @@ encoding-down@~5.0.0: level-errors "^2.0.0" xtend "^4.0.1" -encoding@^0.1.11, encoding@^0.1.12, encoding@^0.1.13: +encoding@^0.1.11, encoding@^0.1.13: version "0.1.13" resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz" integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== @@ -11542,11 +10255,6 @@ entities@^4.2.0, entities@^4.4.0: resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== -entities@~2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz" - integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== - env-paths@^2.2.0, env-paths@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" @@ -11714,7 +10422,7 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: +es5-ext@^0.10.35, es5-ext@^0.10.50: version "0.10.62" resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz" integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== @@ -11752,26 +10460,11 @@ es6-symbol@^3.1.1, es6-symbol@^3.1.3: d "^1.0.1" ext "^1.1.2" -es6-weak-map@^2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz" - integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== - dependencies: - d "1" - es5-ext "^0.10.46" - es6-iterator "^2.0.3" - es6-symbol "^3.1.1" - escalade@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - escape-html@~1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" @@ -11782,7 +10475,7 @@ escape-latex@1.2.0: resolved "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz" integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== @@ -11809,29 +10502,6 @@ escodegen@1.8.x: optionalDependencies: source-map "~0.2.0" -escodegen@^1.13.0, escodegen@^1.8.1: - version "1.14.3" - resolved "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz" - integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== - dependencies: - esprima "^4.0.1" - estraverse "^4.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - -escodegen@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz" - integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionalDependencies: - source-map "~0.6.1" - eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz" @@ -11852,11 +10522,6 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint-visitor-keys@^3.4.1: - 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== - eslint@^5.16.0, eslint@^5.5.0, eslint@^5.6.0: version "5.16.0" resolved "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz" @@ -11899,11 +10564,6 @@ eslint@^5.16.0, eslint@^5.5.0, eslint@^5.6.0: table "^5.2.3" text-table "^0.2.0" -esm@^3.2.25: - version "3.2.25" - resolved "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz" - integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== - espree@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz" @@ -11913,15 +10573,6 @@ espree@^5.0.1: acorn-jsx "^5.0.0" eslint-visitor-keys "^1.0.0" -espree@^9.0.0: - version "9.6.1" - resolved "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== - dependencies: - acorn "^8.9.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" - esprima-extract-comments@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/esprima-extract-comments/-/esprima-extract-comments-1.1.0.tgz" @@ -11934,7 +10585,7 @@ esprima@2.7.x, esprima@^2.7.1: resolved "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz" integrity sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A== -esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: +esprima@^4.0.0, esprima@~4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -11958,7 +10609,7 @@ estraverse@^1.9.1: resolved "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz" integrity sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA== -estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.1.1: version "4.3.0" resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== @@ -12304,7 +10955,7 @@ ethereumjs-util@7.1.5, ethereumjs-util@^7.0.10, ethereumjs-util@^7.0.2, ethereum ethereum-cryptography "^0.1.3" rlp "^2.2.4" -ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereumjs-util@^5.1.2, ethereumjs-util@^5.1.5, ethereumjs-util@^5.2.0: +ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereumjs-util@^5.1.2, ethereumjs-util@^5.1.5: version "5.2.1" resolved "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz" integrity sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ== @@ -12480,14 +11131,6 @@ ethjs-util@0.1.6, ethjs-util@^0.1.3: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" -event-emitter@^0.3.5: - version "0.3.5" - resolved "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz" - integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== - dependencies: - d "1" - es5-ext "~0.10.14" - event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" @@ -12513,11 +11156,6 @@ eventemitter3@^4.0.4: resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events-listener@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/events-listener/-/events-listener-1.1.0.tgz" - integrity sha512-Kd3EgYfODHueq6GzVfs/VUolh2EgJsS8hkO3KpnDrxVjU3eq63eXM2ujXkhPP+OkeUOhL8CxdfZbQXzryb5C4g== - events@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/events/-/events-1.1.1.tgz" @@ -12599,70 +11237,6 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -exegesis-express@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/exegesis-express/-/exegesis-express-2.0.1.tgz" - integrity sha512-8ORl1YRygYGPdR+zcClMqzaU+JQuvdNIw/s0RNwYluxNecEHkDEcXFmO6A5T79p7e48KI8iXJYt6KIn4Z9z4bg== - dependencies: - exegesis "^2.5.7" - -exegesis-express@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/exegesis-express/-/exegesis-express-4.0.0.tgz" - integrity sha512-V2hqwTtYRj0bj43K4MCtm0caD97YWkqOUHFMRCBW5L1x9IjyqOEc7Xa4oQjjiFbeFOSQzzwPV+BzXsQjSz08fw== - dependencies: - exegesis "^4.1.0" - -exegesis@^2.5.7: - version "2.5.7" - resolved "https://registry.npmjs.org/exegesis/-/exegesis-2.5.7.tgz" - integrity sha512-Y0gEY3hgoLa80aMUm8rhhlIW3/KWo4uqN5hKJqok2GLh3maZjRLRC+p0gj33Jw3upAOKOXeRgScT5rtRoMyxwQ== - dependencies: - "@apidevtools/json-schema-ref-parser" "^9.0.3" - ajv "^6.12.2" - body-parser "^1.18.3" - content-type "^1.0.4" - deep-freeze "0.0.1" - events-listener "^1.1.0" - glob "^7.1.3" - json-ptr "^2.2.0" - json-schema-traverse "^1.0.0" - lodash "^4.17.11" - openapi3-ts "^2.0.1" - promise-breaker "^5.0.0" - pump "^3.0.0" - qs "^6.6.0" - raw-body "^2.3.3" - semver "^7.0.0" - -exegesis@^4.1.0: - version "4.1.1" - resolved "https://registry.npmjs.org/exegesis/-/exegesis-4.1.1.tgz" - integrity sha512-PvSqaMOw2absLBgsthtJyVOeCHN4lxQ1dM7ibXb6TfZZJaoXtGELoEAGJRFvdN16+u9kg8oy1okZXRk8VpimWA== - dependencies: - "@apidevtools/json-schema-ref-parser" "^9.0.3" - ajv "^8.3.0" - ajv-formats "^2.1.0" - body-parser "^1.18.3" - content-type "^1.0.4" - deep-freeze "0.0.1" - events-listener "^1.1.0" - glob "^7.1.3" - json-ptr "^3.0.1" - json-schema-traverse "^1.0.0" - lodash "^4.17.11" - openapi3-ts "^3.1.1" - promise-breaker "^6.0.0" - pump "^3.0.0" - qs "^6.6.0" - raw-body "^2.3.3" - semver "^7.0.0" - -exit-code@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/exit-code/-/exit-code-1.0.2.tgz" - integrity sha512-U80QYrKun5np62yRqG6geNRP5TZKU2HF73Bb6IE3XjDHXKlserAdP14tIaP3W9J6ezv84DwbpbRTAtu4FsKcgw== - exit@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" @@ -12748,7 +11322,7 @@ export-files@^2.0.1: dependencies: lazy-cache "^1.0.3" -express@^4.14.0, express@^4.16.4, express@^4.17.1, express@^4.17.6: +express@^4.14.0, express@^4.17.1: version "4.18.2" resolved "https://registry.npmjs.org/express/-/express-4.18.2.tgz" integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== @@ -12961,7 +11535,7 @@ fast-levenshtein@~2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.1.1: +fast-safe-stringify@^2.0.6: version "2.1.1" resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== @@ -12978,13 +11552,6 @@ fast-url-parser@^1.1.3: dependencies: punycode "^1.3.2" -fast-xml-parser@^4.2.2: - version "4.2.7" - resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.7.tgz" - integrity sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig== - dependencies: - strnum "^1.0.5" - fastest-levenshtein@^1.0.7: version "1.0.16" resolved "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz" @@ -12997,13 +11564,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -faye-websocket@0.11.4: - version "0.11.4" - resolved "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== - dependencies: - websocket-driver ">=0.5.1" - fb-watchman@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz" @@ -13018,11 +11578,6 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -fecha@^4.2.0: - version "4.2.3" - resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz" - integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== - fetch-cookie@0.11.0: version "0.11.0" resolved "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.11.0.tgz" @@ -13102,11 +11657,6 @@ file-uri-to-path@1.0.0: resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== -file-uri-to-path@2: - version "2.0.0" - resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz" - integrity sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg== - filelist@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz" @@ -13119,11 +11669,6 @@ filename-regex@^2.0.0: resolved "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz" integrity sha512-BTCqyBaWBTsauvnHiE8i562+EdJj+oUpkqWp2R1iCoR8f6oo8STRu3of7WJJ0TqWtxN50a5YFpzYK4Jj9esYfQ== -filesize@^6.1.0: - version "6.4.0" - resolved "https://registry.npmjs.org/filesize/-/filesize-6.4.0.tgz" - integrity sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ== - fill-keys@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz" @@ -13160,19 +11705,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - finalhandler@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz" @@ -13258,220 +11790,6 @@ findup-sync@^1.0.0: micromatch "^2.3.7" resolve-dir "^0.1.0" -firebase-admin@^11.10.1: - version "11.10.1" - resolved "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.10.1.tgz" - integrity sha512-atv1E6GbuvcvWaD3eHwrjeP5dAVs+EaHEJhu9CThMzPY6In8QYDiUR6tq5SwGl4SdA/GcAU0nhwWc/FSJsAzfQ== - dependencies: - "@fastify/busboy" "^1.2.1" - "@firebase/database-compat" "^0.3.4" - "@firebase/database-types" "^0.10.4" - "@types/node" ">=12.12.47" - jsonwebtoken "^9.0.0" - jwks-rsa "^3.0.1" - node-forge "^1.3.1" - uuid "^9.0.0" - optionalDependencies: - "@google-cloud/firestore" "^6.6.0" - "@google-cloud/storage" "^6.9.5" - -firebase-admin@^9.12.0: - version "9.12.0" - resolved "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.12.0.tgz" - integrity sha512-AtA7OH5RbIFGoc0gZOQgaYC6cdjdhZv4w3XgWoupkPKO1HY+0GzixOuXDa75kFeoVyhIyo4PkLg/GAC1dC1P6w== - dependencies: - "@firebase/database-compat" "^0.1.1" - "@firebase/database-types" "^0.7.2" - "@types/node" ">=12.12.47" - dicer "^0.3.0" - jsonwebtoken "^8.5.1" - jwks-rsa "^2.0.2" - node-forge "^0.10.0" - optionalDependencies: - "@google-cloud/firestore" "^4.5.0" - "@google-cloud/storage" "^5.3.0" - -firebase-functions-test@^0.3.3: - version "0.3.3" - resolved "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-0.3.3.tgz" - integrity sha512-dCppF/2Ztv87IyyBaUQlT1Z05ial5v/3LB0huS2ktXz05yNiID5FVIKtO0/+w9Q7/SThJ8qIDG0hoGDPt4Xcug== - dependencies: - "@types/lodash" "^4.14.104" - lodash "^4.17.5" - -firebase-functions-test@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-3.1.0.tgz" - integrity sha512-yfm9ToguShxmRXb7TINN88zE2bM9gsBbs7vMWVKJAxGcl/n1f/U0sT5k2yho676QIcSqXVSjCONU8W4cUEL+Sw== - dependencies: - "@types/lodash" "^4.14.104" - lodash "^4.17.5" - ts-deepmerge "^2.0.1" - -firebase-functions@^3.15.7: - version "3.24.1" - resolved "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.24.1.tgz" - integrity sha512-GYhoyOV0864HFMU1h/JNBXYNmDk2MlbvU7VO/5qliHX6u/6vhSjTJjlyCG4leDEI8ew8IvmkIC5QquQ1U8hAuA== - dependencies: - "@types/cors" "^2.8.5" - "@types/express" "4.17.3" - cors "^2.8.5" - express "^4.17.1" - lodash "^4.17.14" - node-fetch "^2.6.7" - -firebase-functions@^4.4.1: - version "4.4.1" - resolved "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.4.1.tgz" - integrity sha512-3no53Lg12ToNlPSgLZtAFLQAz6si7ilHvzO8NC3/2EybyUwegpj5YhHwNiCw839lmAWp3znjATJDTvADFiZMrg== - dependencies: - "@types/cors" "^2.8.5" - "@types/express" "4.17.3" - cors "^2.8.5" - express "^4.17.1" - node-fetch "^2.6.7" - protobufjs "^7.2.2" - -firebase-tools@12.4.7: - version "12.4.7" - resolved "https://registry.npmjs.org/firebase-tools/-/firebase-tools-12.4.7.tgz" - integrity sha512-L5nULzh0PElm2OK5lXsj7zkIwBBB4KsGOg0CvCnjdvJ1ROMN2IqffJ0KR/8paXuGWf5SA0VJj2QjG37jFxrAjg== - dependencies: - "@google-cloud/pubsub" "^3.0.1" - abort-controller "^3.0.0" - ajv "^6.12.6" - archiver "^5.0.0" - async-lock "1.3.2" - body-parser "^1.19.0" - chokidar "^3.0.2" - cjson "^0.3.1" - cli-table "0.3.11" - colorette "^2.0.19" - commander "^4.0.1" - configstore "^5.0.1" - cors "^2.8.5" - cross-env "^5.1.3" - cross-spawn "^7.0.3" - csv-parse "^5.0.4" - exegesis "^4.1.0" - exegesis-express "^4.0.0" - express "^4.16.4" - filesize "^6.1.0" - form-data "^4.0.0" - fs-extra "^10.1.0" - glob "^7.1.2" - google-auth-library "^7.11.0" - inquirer "^8.2.0" - js-yaml "^3.13.1" - jsonwebtoken "^9.0.0" - leven "^3.1.0" - libsodium-wrappers "^0.7.10" - lodash "^4.17.21" - marked "^4.0.14" - marked-terminal "^5.1.1" - mime "^2.5.2" - minimatch "^3.0.4" - morgan "^1.10.0" - node-fetch "^2.6.7" - open "^6.3.0" - ora "^5.4.1" - p-limit "^3.0.1" - portfinder "^1.0.32" - progress "^2.0.3" - proxy-agent "^6.3.0" - request "^2.87.0" - retry "^0.13.1" - rimraf "^3.0.0" - semver "^7.5.2" - stream-chain "^2.2.4" - stream-json "^1.7.3" - strip-ansi "^6.0.1" - superstatic "^9.0.3" - tar "^6.1.11" - tcp-port-used "^1.0.2" - tmp "^0.2.1" - triple-beam "^1.3.0" - universal-analytics "^0.5.3" - update-notifier-cjs "^5.1.6" - uuid "^8.3.2" - winston "^3.0.0" - winston-transport "^4.4.0" - ws "^7.2.3" - -firebase-tools@9.20.0: - version "9.20.0" - resolved "https://registry.npmjs.org/firebase-tools/-/firebase-tools-9.20.0.tgz" - integrity sha512-/5LzkZtW8aC57syHf34FXY1w6g9unb7qdvtlYROdJA33sk2xsWsJmuvtJylhYhTNX8zrwFsmiTHRlaBxA9YWtg== - dependencies: - "@google-cloud/pubsub" "^2.7.0" - "@types/archiver" "^5.1.0" - JSONStream "^1.2.1" - abort-controller "^3.0.0" - ajv "^6.12.6" - archiver "^5.0.0" - body-parser "^1.19.0" - chokidar "^3.0.2" - cjson "^0.3.1" - cli-color "^1.2.0" - cli-table "^0.3.1" - commander "^4.0.1" - configstore "^5.0.1" - cors "^2.8.5" - cross-env "^5.1.3" - cross-spawn "^7.0.1" - csv-streamify "^3.0.4" - dotenv "^6.1.0" - exegesis "^2.5.7" - exegesis-express "^2.0.0" - exit-code "^1.0.2" - express "^4.16.4" - filesize "^6.1.0" - fs-extra "^5.0.0" - glob "^7.1.2" - google-auth-library "^6.1.3" - inquirer "~6.3.1" - js-yaml "^3.13.1" - jsonwebtoken "^8.5.1" - leven "^3.1.0" - lodash "^4.17.21" - marked "^0.7.0" - marked-terminal "^3.3.0" - mime "^2.5.2" - minimatch "^3.0.4" - morgan "^1.10.0" - node-fetch "^2.6.1" - open "^6.3.0" - ora "^3.4.0" - portfinder "^1.0.23" - progress "^2.0.3" - proxy-agent "^5.0.0" - request "^2.87.0" - rimraf "^3.0.0" - semver "^5.7.1" - superstatic "^7.1.0" - tar "^4.3.0" - tcp-port-used "^1.0.1" - tmp "0.0.33" - triple-beam "^1.3.0" - tweetsodium "0.0.5" - universal-analytics "^0.4.16" - unzipper "^0.10.10" - update-notifier "^5.1.0" - uuid "^3.0.0" - winston "^3.0.0" - winston-transport "^4.4.0" - ws "^7.2.3" - -flat-arguments@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/flat-arguments/-/flat-arguments-1.0.2.tgz" - integrity sha512-ZIkB09bqQdKP9buPOiZcS/4HK3q992C5q62qAE72d0xWAXfaSbP840BZYUBgHRkzdx6jYRIpKT4ur+Nay/JRlg== - dependencies: - array-flatten "^1.0.0" - as-array "^1.0.0" - lodash.isarguments "^3.0.0" - lodash.isobject "^3.0.0" - flat-cache@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz" @@ -13503,11 +11821,6 @@ flow-stoplight@^1.0.0: resolved "https://registry.npmjs.org/flow-stoplight/-/flow-stoplight-1.0.0.tgz" integrity sha512-rDjbZUKpN8OYhB0IE/vY/I8UWO/602IIJEU/76Tv4LvYnwHCk0BCsvz4eRr9n+FQcri7L5cyaXOo0+/Kh4HisA== -fn.name@1.x.x: - version "1.1.0" - resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz" - integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== - follow-redirects@1.5.10: version "1.5.10" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz" @@ -13603,16 +11916,6 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -formidable@^2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz" - integrity sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g== - dependencies: - dezalgo "^1.0.4" - hexoid "^1.0.0" - once "^1.4.0" - qs "^6.11.0" - forwarded@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" @@ -13661,15 +11964,6 @@ fs-extra@^0.30.0: path-is-absolute "^1.0.0" rimraf "^2.2.8" -fs-extra@^10.1.0: - 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== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs-extra@^11.1.0: version "11.1.1" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz" @@ -13775,24 +12069,6 @@ fsevents@~2.1.1, fsevents@~2.1.2: resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== -fstream@^1.0.12: - version "1.0.12" - resolved "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz" - integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" - -ftp@^0.3.10: - version "0.3.10" - resolved "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz" - integrity sha512-faFVML1aBx2UoDStmLwv2Wptt4vw5x03xxX172nhA5Y5HBshW5JweqQ2W4xL4dezQTG8inJsuYcpPHHU3X5OTQ== - dependencies: - readable-stream "1.1.x" - xregexp "2.0.0" - function-bind@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" @@ -13903,7 +12179,7 @@ gaxios@^4.0.0: is-stream "^2.0.0" node-fetch "^2.6.7" -gaxios@^5.0.0, gaxios@^5.0.1: +gaxios@^5.0.0: version "5.1.3" resolved "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz" integrity sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA== @@ -13929,7 +12205,7 @@ gcp-metadata@^4.2.0: gaxios "^4.0.0" json-bigint "^1.0.0" -gcp-metadata@^5.0.0, gcp-metadata@^5.3.0: +gcp-metadata@^5.0.0: version "5.3.0" resolved "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz" integrity sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w== @@ -13949,13 +12225,6 @@ gcs-resumable-upload@^1.0.0: pumpify "^1.5.1" stream-events "^1.0.4" -generate-function@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz" - integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== - dependencies: - is-property "^1.0.2" - generate-password@^1.5.1: version "1.7.0" resolved "https://registry.npmjs.org/generate-password/-/generate-password-1.7.0.tgz" @@ -14091,28 +12360,6 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" -get-uri@3: - version "3.0.2" - resolved "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz" - integrity sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg== - dependencies: - "@tootallnate/once" "1" - data-uri-to-buffer "3" - debug "4" - file-uri-to-path "2" - fs-extra "^8.1.0" - ftp "^0.3.10" - -get-uri@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.1.tgz" - integrity sha512-7ZqONUVqaabogsYNWlYj0t3YZaL6dhuEueZXGF+/YVmf6dHmaFg8/6psJKqhx9QykIDKzpGcy2cn4oV4YC7V/Q== - dependencies: - basic-ftp "^5.0.2" - data-uri-to-buffer "^5.0.1" - debug "^4.3.4" - fs-extra "^8.1.0" - get-value@^1.1.5: version "1.3.1" resolved "https://registry.npmjs.org/get-value/-/get-value-1.3.1.tgz" @@ -14128,11 +12375,6 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== -getopts@2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz" - integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA== - getpass@^0.1.1: version "0.1.7" resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz" @@ -14282,20 +12524,6 @@ glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob-slash@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/glob-slash/-/glob-slash-1.0.0.tgz" - integrity sha512-ZwFh34WZhZX28ntCMAP1mwyAJkn8+Omagvt/GvA+JQM/qgT0+MR2NPF3vhvgdshfdvDyGZXs8fPXW84K32Wjuw== - -glob-slasher@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/glob-slasher/-/glob-slasher-1.0.1.tgz" - integrity sha512-5MUzqFiycIKLMD1B0dYOE4hGgLLUZUNGGYO4BExdwT32wUwW3DBOE7lMQars7vB1q43Fb3Tyt+HmgLKsJhDYdg== - dependencies: - glob-slash "^1.0.0" - lodash.isobject "^2.4.1" - toxic "^1.0.0" - glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" @@ -14399,7 +12627,7 @@ glob@^6.0.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.3, glob@~7.2.3: +glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.2.3: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -14411,7 +12639,7 @@ glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, gl once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.0, glob@^8.0.1: +glob@^8.0.1: version "8.1.0" resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== @@ -14422,20 +12650,6 @@ glob@^8.0.0, glob@^8.0.1: minimatch "^5.0.1" once "^1.3.0" -global-dirs@^2.0.1: - version "2.1.0" - resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz" - integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== - dependencies: - ini "1.3.7" - -global-dirs@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz" - integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== - dependencies: - ini "2.0.0" - global-modules@1.0.0, global-modules@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz" @@ -14548,22 +12762,7 @@ google-auth-library@^3.0.0, google-auth-library@^3.1.1: lru-cache "^5.0.0" semver "^5.5.0" -google-auth-library@^6.1.3: - version "6.1.6" - resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz" - integrity sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ== - dependencies: - arrify "^2.0.0" - base64-js "^1.3.0" - ecdsa-sig-formatter "^1.0.11" - fast-text-encoding "^1.0.0" - gaxios "^4.0.0" - gcp-metadata "^4.2.0" - gtoken "^5.0.4" - jws "^4.0.0" - lru-cache "^6.0.0" - -google-auth-library@^7.0.0, google-auth-library@^7.11.0, google-auth-library@^7.14.0, google-auth-library@^7.14.1: +google-auth-library@^7.14.0: version "7.14.1" resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz" integrity sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA== @@ -14578,44 +12777,10 @@ google-auth-library@^7.0.0, google-auth-library@^7.11.0, google-auth-library@^7. jws "^4.0.0" lru-cache "^6.0.0" -google-auth-library@^8.0.1, google-auth-library@^8.0.2: - version "8.9.0" - resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz" - integrity sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg== - dependencies: - arrify "^2.0.0" - base64-js "^1.3.0" - ecdsa-sig-formatter "^1.0.11" - fast-text-encoding "^1.0.0" - gaxios "^5.0.0" - gcp-metadata "^5.3.0" - gtoken "^6.1.0" - jws "^4.0.0" - lru-cache "^6.0.0" - -google-gax@2.30.3: - version "2.30.3" - resolved "https://registry.npmjs.org/google-gax/-/google-gax-2.30.3.tgz" - integrity sha512-Zsd6hbJBMvAcJS3cYpAsmupvfsxygFR2meUZJcGeR7iUqYHCR/1Hf2aQNB9srrlXQMm91pNiUvW0Kz6Qld8QkA== - dependencies: - "@grpc/grpc-js" "~1.6.0" - "@grpc/proto-loader" "0.6.9" - "@types/long" "^4.0.0" - abort-controller "^3.0.0" - duplexify "^4.0.0" - fast-text-encoding "^1.0.3" - google-auth-library "^7.14.0" - is-stream-ended "^0.1.4" - node-fetch "^2.6.1" - object-hash "^3.0.0" - proto3-json-serializer "^0.1.8" - protobufjs "6.11.2" - retry-request "^4.0.0" - -google-gax@^0.25.0: - version "0.25.6" - resolved "https://registry.npmjs.org/google-gax/-/google-gax-0.25.6.tgz" - integrity sha512-+CVtOSLQt42mwVvJJirhBiAvWsp8zKeb9zW5Wy3wyvb3VG9OugHzZpwvYO9D4yNPPspe7L9CpIs80I5nUJlS8w== +google-gax@^0.25.0: + version "0.25.6" + resolved "https://registry.npmjs.org/google-gax/-/google-gax-0.25.6.tgz" + integrity sha512-+CVtOSLQt42mwVvJJirhBiAvWsp8zKeb9zW5Wy3wyvb3VG9OugHzZpwvYO9D4yNPPspe7L9CpIs80I5nUJlS8w== dependencies: "@grpc/grpc-js" "^0.3.0" "@grpc/proto-loader" "^0.4.0" @@ -14651,32 +12816,6 @@ google-gax@^2.0.1, google-gax@^2.24.1: protobufjs "6.11.3" retry-request "^4.0.0" -google-gax@^3.5.7, google-gax@^3.6.1: - version "3.6.1" - resolved "https://registry.npmjs.org/google-gax/-/google-gax-3.6.1.tgz" - integrity sha512-g/lcUjGcB6DSw2HxgEmCDOrI/CByOwqRvsuUvNalHUK2iPPPlmAIpbMbl62u0YufGMr8zgE3JL7th6dCb1Ry+w== - dependencies: - "@grpc/grpc-js" "~1.8.0" - "@grpc/proto-loader" "^0.7.0" - "@types/long" "^4.0.0" - "@types/rimraf" "^3.0.2" - abort-controller "^3.0.0" - duplexify "^4.0.0" - fast-text-encoding "^1.0.3" - google-auth-library "^8.0.2" - is-stream-ended "^0.1.4" - node-fetch "^2.6.1" - object-hash "^3.0.0" - proto3-json-serializer "^1.0.0" - protobufjs "7.2.4" - protobufjs-cli "1.1.1" - retry-request "^5.0.0" - -google-libphonenumber@^3.2.15: - version "3.2.33" - resolved "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.33.tgz" - integrity sha512-1QKCvAlfq8zY1mviORI9lDzM3I/hwm9+h0CwYBTLq59DBbSHMd5zBOLqHZFiBLicRpwIz46Nynvbywj1XApKvA== - google-libphonenumber@^3.2.27: version "3.2.32" resolved "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.32.tgz" @@ -14697,13 +12836,6 @@ google-p12-pem@^3.1.3: dependencies: node-forge "^1.3.1" -google-p12-pem@^4.0.0: - version "4.0.1" - resolved "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz" - integrity sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ== - dependencies: - node-forge "^1.3.1" - google-proto-files@^0.20.0: version "0.20.0" resolved "https://registry.npmjs.org/google-proto-files/-/google-proto-files-0.20.0.tgz" @@ -14759,7 +12891,7 @@ got@12.1.0: p-cancelable "^3.0.0" responselike "^2.0.0" -got@9.6.0, got@^9.6.0: +got@9.6.0: version "9.6.0" resolved "https://registry.npmjs.org/got/-/got-9.6.0.tgz" integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== @@ -14813,12 +12945,7 @@ got@^7.1.0: url-parse-lax "^1.0.0" url-to-options "^1.0.1" -graceful-fs@4.2.10: - version "4.2.10" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - -graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.6, graceful-fs@^4.2.9: 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== @@ -14892,15 +13019,6 @@ gtoken@^5.0.4: google-p12-pem "^3.1.3" jws "^4.0.0" -gtoken@^6.1.0: - version "6.1.2" - resolved "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz" - integrity sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ== - dependencies: - gaxios "^5.0.1" - google-p12-pem "^4.0.0" - jws "^4.0.0" - handlebars@^4.0.1: version "4.7.7" resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz" @@ -14943,13 +13061,6 @@ hard-rejection@^2.1.0: resolved "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz" integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" - integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== - dependencies: - ansi-regex "^2.0.0" - has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz" @@ -15047,11 +13158,6 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - has@^1.0.3, has@~1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" @@ -15068,7 +13174,7 @@ hash-base@^3.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" -hash-stream-validation@^0.2.1, hash-stream-validation@^0.2.2: +hash-stream-validation@^0.2.1: version "0.2.4" resolved "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz" integrity sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ== @@ -15125,11 +13231,6 @@ header-case@^1.0.0: no-case "^2.2.0" upper-case "^1.1.3" -heap-js@^2.2.0: - version "2.3.0" - resolved "https://registry.npmjs.org/heap-js/-/heap-js-2.3.0.tgz" - integrity sha512-E5303mzwQ+4j/n2J0rDvEPBN7GKjhis10oHiYOgjxsmxYgqG++hz9NyLLOXttzH8as/DyiBHYpUrJTZWYaMo8Q== - hexer@^1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/hexer/-/hexer-1.5.0.tgz" @@ -15140,11 +13241,6 @@ hexer@^1.5.0: process "^0.10.0" xtend "^4.0.0" -hexoid@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz" - integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== - highlight.js@^10.2.0, highlight.js@^10.4.1: version "10.7.3" resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz" @@ -15164,11 +13260,6 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -home-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/home-dir/-/home-dir-1.0.0.tgz" - integrity sha512-PPAP0BMY72XQ0sYwFow8EgHwUYfptkZusnZEGHkBjdKRXIYcVFsbEViqU4k8VrJWf0m7wMr9gscQX9klJYh7zg== - homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz" @@ -15271,12 +13362,7 @@ http-https@^1.0.0: resolved "https://registry.npmjs.org/http-https/-/http-https-1.0.0.tgz" integrity sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg== -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - -http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1: +http-proxy-agent@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz" integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== @@ -15294,14 +13380,6 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -http-proxy-agent@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz" - integrity sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ== - dependencies: - agent-base "^7.1.0" - debug "^4.3.4" - http-response-object@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-3.0.2.tgz#7f435bb210454e4360d074ef1f989d5ea8aa9810" @@ -15334,14 +13412,6 @@ http2-wrapper@^2.1.10: quick-lru "^5.1.1" resolve-alpn "^1.2.0" -https-proxy-agent@5, https-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== - dependencies: - agent-base "6" - debug "4" - https-proxy-agent@^2.2.1: version "2.2.4" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz" @@ -15350,12 +13420,12 @@ https-proxy-agent@^2.2.1: agent-base "^4.3.0" debug "^3.1.0" -https-proxy-agent@^7.0.2: - version "7.0.2" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz" - integrity sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA== +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== dependencies: - agent-base "^7.0.2" + agent-base "6" debug "4" human-signals@^2.1.0: @@ -15404,14 +13474,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.5.0: - version "0.5.2" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz" - integrity sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@^0.6.2, iconv-lite@^0.6.3: +iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -15500,11 +13563,6 @@ import-in-the-middle@1.4.2: cjs-module-lexer "^1.2.2" module-details-from-path "^1.0.3" -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz" - integrity sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A== - import-local@^3.0.2: version "3.1.0" resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz" @@ -15541,7 +13599,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -15551,16 +13609,6 @@ inherits@2.0.3: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== -ini@1.3.7: - version "1.3.7" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz" - integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== - -ini@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: version "1.3.8" resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" @@ -15617,7 +13665,7 @@ inquirer@^7.0.5: strip-ansi "^6.0.0" through "^2.3.6" -inquirer@^8.2.0, inquirer@^8.2.4: +inquirer@^8.2.4: version "8.2.6" resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz" integrity sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg== @@ -15638,30 +13686,6 @@ inquirer@^8.2.0, inquirer@^8.2.4: through "^2.3.6" wrap-ansi "^6.0.1" -inquirer@~6.3.1: - version "6.3.1" - resolved "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz" - integrity sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA== - dependencies: - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - lodash "^4.17.11" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" - -install-artifact-from-github@^1.3.3: - version "1.3.3" - resolved "https://registry.npmjs.org/install-artifact-from-github/-/install-artifact-from-github-1.3.3.tgz" - integrity sha512-x79SL0d8WOi1ZjXSTUqqs0GPQZ92YArJAN9O46wgU9wdH2U9ecyyhB9YGDbPe2OLV4ptmt6AZYRQZ2GydQZosQ== - internal-slot@^1.0.4, internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz" @@ -15676,11 +13700,6 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== -interpret@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz" - integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== - invariant@2: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" @@ -15703,16 +13722,6 @@ io-ts@2.0.1: resolved "https://registry.npmjs.org/io-ts/-/io-ts-2.0.1.tgz" integrity sha512-RezD+WcCfW4VkMkEcQWL/Nmy/nqsWTvTYg7oUmTGzglvSSV2P9h2z1PVeREPFf0GWNzruYleAt1XCMQZSg1xxQ== -ip-regex@^4.1.0: - version "4.3.0" - resolved "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz" - integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== - -ip@^1.1.5, ip@^1.1.8: - version "1.1.8" - resolved "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz" - integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg== - ip@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz" @@ -15767,11 +13776,6 @@ is-arrayish@^0.2.1: resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-base64@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/is-base64/-/is-base64-1.1.0.tgz" @@ -15995,22 +13999,6 @@ is-hex-prefixed@1.0.0: resolved "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz" integrity sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA== -is-installed-globally@^0.3.1: - version "0.3.2" - resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz" - integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== - dependencies: - global-dirs "^2.0.1" - is-path-inside "^3.0.1" - -is-installed-globally@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz" - integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== - dependencies: - global-dirs "^3.0.0" - is-path-inside "^3.0.2" - is-interactive@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz" @@ -16043,16 +14031,6 @@ is-negative-zero@^2.0.2: resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== -is-npm@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz" - integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== - -is-npm@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz" - integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== - is-number-object@^1.0.4: version "1.0.7" resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz" @@ -16099,11 +14077,6 @@ is-object@^1.0.1, is-object@~1.0.1: resolved "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz" integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== -is-path-inside@^3.0.1, is-path-inside@^3.0.2: - 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== - is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" @@ -16136,16 +14109,6 @@ is-primitive@^2.0.0: resolved "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz" integrity sha512-N3w1tFaRfk3UrPfqeRyD+GYDASU3W5VinKhlORy8EWVf/sIdDL9GAcew85XmktCfH+ngG7SRXEVDoO18WMdB/Q== -is-promise@^2.2.2: - version "2.2.2" - resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz" - integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== - -is-property@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" - integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== - is-regex@^1.0.4, is-regex@^1.1.4, is-regex@~1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" @@ -16266,11 +14229,6 @@ is-upper-case@^1.1.0: dependencies: upper-case "^1.1.0" -is-url@^1.2.2, is-url@^1.2.4: - version "1.2.4" - resolved "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz" - integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== - is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz" @@ -16310,20 +14268,6 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - -is2@^2.0.6: - version "2.0.9" - resolved "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz" - integrity sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g== - dependencies: - deep-is "^0.1.3" - ip-regex "^4.1.0" - is-url "^1.2.4" - isarray@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" @@ -16479,7 +14423,7 @@ iterate-iterator@^1.0.1: resolved "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.2.tgz" integrity sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw== -iterate-value@^1.0.0, iterate-value@^1.0.2: +iterate-value@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz" integrity sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ== @@ -16968,11 +14912,6 @@ jest@^29.0.2: import-local "^3.0.2" jest-cli "^29.5.0" -jju@^1.1.0: - version "1.4.0" - resolved "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz" - integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA== - jmespath@0.16.0: version "0.16.0" resolved "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz" @@ -16989,27 +14928,6 @@ joi@^17.3.0: "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" -join-path@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/join-path/-/join-path-1.1.1.tgz" - integrity sha512-jnt9OC34sLXMLJ6YfPQ2ZEKrR9mB5ZbSnQb4LPaOx1c5rTzxpR33L18jjp0r75mGGTJmsil3qwN1B5IBeTnSSA== - dependencies: - as-array "^2.0.0" - url-join "0.0.1" - valid-url "^1" - -jose@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz" - integrity sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg== - dependencies: - "@panva/asn1.js" "^1.0.0" - -jose@^4.10.4: - version "4.14.6" - resolved "https://registry.npmjs.org/jose/-/jose-4.14.6.tgz" - integrity sha512-EqJPEUlZD0/CSUMubKtMaYUOtWe91tZXTWMJZoKSbLk+KtdhNdcvppH8lA9XwVu2V4Ailvsj0GBZJ2ZwDjfesQ== - js-sdsl@^4.1.4: version "4.4.0" resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz" @@ -17058,44 +14976,11 @@ js-yaml@4.1.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -js2xmlparser@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz" - integrity sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA== - dependencies: - xmlcreate "^2.0.4" - -jsbi@^3.1.1: - version "3.2.5" - resolved "https://registry.npmjs.org/jsbi/-/jsbi-3.2.5.tgz" - integrity sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ== - jsbn@~0.1.0: version "0.1.1" resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== -jsdoc@^4.0.0: - version "4.0.2" - resolved "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz" - integrity sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg== - dependencies: - "@babel/parser" "^7.20.15" - "@jsdoc/salty" "^0.2.1" - "@types/markdown-it" "^12.2.3" - bluebird "^3.7.2" - catharsis "^0.9.0" - escape-string-regexp "^2.0.0" - js2xmlparser "^4.0.2" - klaw "^3.0.0" - markdown-it "^12.3.2" - markdown-it-anchor "^8.4.1" - marked "^4.0.10" - mkdirp "^1.0.4" - requizzle "^0.2.3" - strip-json-comments "^3.1.0" - underscore "~1.13.2" - jsesc@^2.5.1: version "2.5.2" resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" @@ -17135,13 +15020,6 @@ json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^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== -json-parse-helpfulerror@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz" - integrity sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg== - dependencies: - jju "^1.1.0" - json-pointer@^0.6.1: version "0.6.2" resolved "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz" @@ -17149,18 +15027,6 @@ json-pointer@^0.6.1: dependencies: foreach "^2.0.4" -json-ptr@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/json-ptr/-/json-ptr-2.2.0.tgz" - integrity sha512-w9f6/zhz4kykltXMG7MLJWMajxiPj0q+uzQPR1cggNAE/sXoq/C5vjUb/7QNcC3rJsVIIKy37ALTXy1O+3c8QQ== - dependencies: - tslib "^2.2.0" - -json-ptr@^3.0.1: - version "3.1.1" - resolved "https://registry.npmjs.org/json-ptr/-/json-ptr-3.1.1.tgz" - integrity sha512-SiSJQ805W1sDUCD1+/t1/1BIrveq2Fe9HJqENxZmMCILmrPI7WhS/pePpIOx85v6/H2z1Vy7AI08GV2TzfXocg== - json-rpc-engine@^5.3.0: version "5.4.0" resolved "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-5.4.0.tgz" @@ -17317,22 +15183,6 @@ jsonwebtoken@^8.5.1: ms "^2.1.1" semver "^5.6.0" -jsonwebtoken@^9.0.0: - version "9.0.2" - resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz" - integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^7.5.4" - jsprim@^1.2.2: version "1.4.2" resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz" @@ -17371,31 +15221,7 @@ jwa@^2.0.0: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" -jwks-rsa@^2.0.2: - version "2.1.5" - resolved "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.1.5.tgz" - integrity sha512-IODtn1SwEm7n6GQZnQLY0oxKDrMh7n/jRH1MzE8mlxWMrh2NnMyOsXTebu8vJ1qCpmuTJcL4DdiE0E4h8jnwsA== - dependencies: - "@types/express" "^4.17.14" - "@types/jsonwebtoken" "^8.5.9" - debug "^4.3.4" - jose "^2.0.6" - limiter "^1.1.5" - lru-memoizer "^2.1.4" - -jwks-rsa@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz" - integrity sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw== - dependencies: - "@types/express" "^4.17.14" - "@types/jsonwebtoken" "^9.0.0" - debug "^4.3.4" - jose "^4.10.4" - limiter "^1.1.5" - lru-memoizer "^2.1.4" - -jws@3.x.x, jws@^3.1.5, jws@^3.2.2: +jws@^3.1.5, jws@^3.2.2: version "3.2.2" resolved "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz" integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== @@ -17411,15 +15237,6 @@ jws@^4.0.0: jwa "^2.0.0" safe-buffer "^5.0.1" -keccak256@^1.0.0: - version "1.0.6" - resolved "https://registry.npmjs.org/keccak256/-/keccak256-1.0.6.tgz" - integrity sha512-8GLiM01PkdJVGUhR1e6M/AvWnSqYS0HaERI+K/QtStGDGlSTx2B1zTqZk4Zlqu5TxHJNTxWAdP9Y+WI50OApUw== - dependencies: - bn.js "^5.2.0" - buffer "^6.0.3" - keccak "^3.0.2" - keccak@3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz" @@ -17447,7 +15264,7 @@ keccak@^1.0.2: nan "^2.2.1" safe-buffer "^5.1.0" -keccak@^3.0.0, keccak@^3.0.2: +keccak@^3.0.0: version "3.0.3" resolved "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz" integrity sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ== @@ -17528,13 +15345,6 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" -klaw@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz" - integrity sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g== - dependencies: - graceful-fs "^4.1.9" - kleur@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz" @@ -17545,38 +15355,6 @@ kleur@^3.0.3: resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -knex@^2.1.0: - version "2.5.1" - resolved "https://registry.npmjs.org/knex/-/knex-2.5.1.tgz" - integrity sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA== - dependencies: - colorette "2.0.19" - commander "^10.0.0" - debug "4.3.4" - escalade "^3.1.1" - esm "^3.2.25" - get-package-type "^0.1.0" - getopts "2.3.0" - interpret "^2.2.0" - lodash "^4.17.21" - pg-connection-string "2.6.1" - rechoir "^0.8.0" - resolve-from "^5.0.0" - tarn "^3.0.2" - tildify "2.0.0" - -kuler@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz" - integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== - -latest-version@^5.0.0, latest-version@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - lazy-cache@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.1.0.tgz" @@ -17594,13 +15372,6 @@ lazy-cache@^1.0.3: resolved "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz" integrity sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ== -lazystream@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz" - integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== - dependencies: - readable-stream "^2.0.5" - lcid@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz" @@ -17911,23 +15682,6 @@ libnpmpublish@^6.0.4: semver "^7.3.7" ssri "^9.0.0" -libsodium-wrappers@^0.7.10: - version "0.7.11" - resolved "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.11.tgz" - integrity sha512-SrcLtXj7BM19vUKtQuyQKiQCRJPgbpauzl3s0rSwD+60wtHqSUuqcoawlMDheCJga85nKOQwxNYQxf/CKAvs6Q== - dependencies: - libsodium "^0.7.11" - -libsodium@^0.7.11: - version "0.7.11" - resolved "https://registry.npmjs.org/libsodium/-/libsodium-0.7.11.tgz" - integrity sha512-WPfJ7sS53I2s4iM58QxY3Inb83/6mjlYgcmZs7DJsvDlnmVUwNinBCi5vBT43P6bHRy01O4zsMU2CoVR6xJ40A== - -limiter@^1.1.5: - version "1.1.5" - resolved "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz" - integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== - lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" @@ -17938,18 +15692,6 @@ lines-and-columns@~2.0.3: resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz" integrity sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w== -linkify-it@^3.0.1: - version "3.0.3" - resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz" - integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== - dependencies: - uc.micro "^1.0.1" - -listenercount@~1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz" - integrity sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ== - load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz" @@ -18027,28 +15769,11 @@ lodash-es@^4.2.1: resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== -lodash._isnative@~2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz" - integrity sha512-BOlKGKNHhCHswGOWtmVb5zBygyxN7EmTuzVOSQI6QSoGhG+kvv71gICFS1TBpnqvT1n53txK8CDK3u5D2/GZxQ== - -lodash._objecttypes@~2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz" - integrity sha512-XpqGh1e7hhkOzftBfWE7zt+Yn9mVHFkDhicVttvKLsoCMLVVL+xTQjfjB4X4vtznauxv0QZ5ZAeqjvat0dh62Q== - lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz" integrity sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA== -lodash._shimkeys@~2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz" - integrity sha512-lBrglYxLD/6KAJ8IEa5Lg+YHgNAL7FyKqXg4XOUI+Du/vtniLs1ZqS+yHNKPkK54waAgkdUnDOYaWf+rv4B+AA== - dependencies: - lodash._objecttypes "~2.4.1" - lodash.assign@^4.0.3, lodash.assign@^4.0.6: version "4.2.0" resolved "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz" @@ -18069,31 +15794,11 @@ lodash.clone@^4.5.0: resolved "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz" integrity sha512-GhrVeweiTD6uTmmn5hV/lzgCQhccwReIVRLHp7LT4SopOjqEZ5BbX8b5WWEtAKasjmy8hR7ZPwsYlxRCku5odg== -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz" - integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== - lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== -lodash.defaults@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz" - integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== - -lodash.difference@^4.5.0: - version "4.5.0" - resolved "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz" - integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA== - -lodash.flatten@^4.4.0: - version "4.4.0" - resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz" - integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== - lodash.get@~4.4.2: version "4.4.2" resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz" @@ -18109,16 +15814,6 @@ lodash.includes@^4.3.0: resolved "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz" integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== -lodash.isarguments@2.4.x: - version "2.4.1" - resolved "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-2.4.1.tgz" - integrity sha512-CyMQjsJqDgXL8M2xYAP6V2dlVXli8IhWXLsk19uXxiL9/qISjzQXyWtxsumR2q4CnR9FjCnxpuIO1d9KSKBcyA== - -lodash.isarguments@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz" - integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== - lodash.isboolean@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz" @@ -18144,18 +15839,6 @@ lodash.isnumber@^3.0.3: resolved "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz" integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== -lodash.isobject@^2.4.1, lodash.isobject@~2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz" - integrity sha512-sTebg2a1PoicYEZXD5PBdQcTlIJ6hUslrlWr7iV0O7n+i4596s2NQ9I5CaZ5FbXSfya/9WQsrYLANUJv9paYVA== - dependencies: - lodash._objecttypes "~2.4.1" - -lodash.isobject@^3.0.0: - version "3.0.2" - resolved "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz" - integrity sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA== - lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz" @@ -18166,15 +15849,6 @@ lodash.isstring@^4.0.1: resolved "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz" integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== -lodash.keys@~2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz" - integrity sha512-ZpJhwvUXHSNL5wYd1RM6CUa2ZuqorG9ngoJ9Ix5Cce+uX7I5O/E06FCJdhSZ33b5dVyeQDnIlWH7B2s5uByZ7g== - dependencies: - lodash._isnative "~2.4.1" - lodash._shimkeys "~2.4.1" - lodash.isobject "~2.4.1" - lodash.memoize@4.x: version "4.1.2" resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" @@ -18215,35 +15889,16 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "^3.0.0" -lodash.union@^4.6.0: - version "4.6.0" - resolved "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz" - integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== - -lodash.values@^2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz" - integrity sha512-fQwubKvj2Nox2gy6YnjFm8C1I6MIlzKUtBB+Pj7JGtloGqDDL5CPRr4DUUFWPwXWwAl2k3f4C3Aw8H1qAPB9ww== - dependencies: - lodash.keys "~2.4.1" - lodash.values@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz" integrity sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q== -lodash@4.17.21, lodash@^4.16.4, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.19: +lodash@4.17.21, lodash@^4.16.4, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.2.1, lodash@~4.17.19: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@2.2.0, log-symbols@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== - dependencies: - chalk "^2.0.1" - log-symbols@3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz" @@ -18259,17 +15914,12 @@ log-symbols@4.1.0, log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -logform@^2.3.2, logform@^2.4.0: - version "2.5.1" - resolved "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz" - integrity sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg== +log-symbols@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== dependencies: - "@colors/colors" "1.5.0" - "@types/triple-beam" "^1.3.2" - fecha "^4.2.0" - ms "^2.1.1" - safe-stable-stringify "^2.3.1" - triple-beam "^1.3.0" + chalk "^2.0.1" loglevel@^1.6.1, loglevel@^1.6.8: version "1.8.1" @@ -18342,11 +15992,6 @@ lowercase-keys@^3.0.0: resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.1.tgz" integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ== -lru-cache@^10.0.1, "lru-cache@^9.1.1 || ^10.0.0": - version "10.0.1" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz" - integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== - lru-cache@^5.0.0, lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" @@ -18361,33 +16006,15 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-cache@^7.14.1, lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: +lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: version "7.18.3" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== -lru-cache@~4.0.0: - version "4.0.2" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz" - integrity sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw== - dependencies: - pseudomap "^1.0.1" - yallist "^2.0.0" - -lru-memoizer@^2.1.4: - version "2.2.0" - resolved "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz" - integrity sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw== - dependencies: - lodash.clonedeep "^4.5.0" - lru-cache "~4.0.0" - -lru-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz" - integrity sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ== - dependencies: - es5-ext "~0.10.2" +"lru-cache@^9.1.1 || ^10.0.0": + version "10.0.1" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz" + integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== ltgt@2.2.1, ltgt@^2.1.2, ltgt@~2.2.0: version "2.2.1" @@ -18399,11 +16026,6 @@ lunr@^2.3.9: resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== -luxon@~3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz" - integrity sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg== - make-dir@^1.0.0: version "1.3.0" resolved "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz" @@ -18474,28 +16096,6 @@ make-fetch-happen@^11.0.3: socks-proxy-agent "^7.0.0" ssri "^10.0.0" -make-fetch-happen@^9.1.0: - version "9.1.0" - resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz" - integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== - dependencies: - agentkeepalive "^4.1.3" - cacache "^15.2.0" - http-cache-semantics "^4.1.0" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^6.0.0" - minipass "^3.1.3" - minipass-collect "^1.0.2" - minipass-fetch "^1.3.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - negotiator "^0.6.2" - promise-retry "^2.0.1" - socks-proxy-agent "^6.0.0" - ssri "^8.0.0" - makeerror@1.0.12: version "1.0.12" resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" @@ -18532,66 +16132,16 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -markdown-it-anchor@^8.4.1: - version "8.6.7" - resolved "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz" - integrity sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA== - -markdown-it@^12.3.2: - version "12.3.2" - resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz" - integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== - dependencies: - argparse "^2.0.1" - entities "~2.1.0" - linkify-it "^3.0.1" - mdurl "^1.0.1" - uc.micro "^1.0.5" - markdown-table@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.3.tgz#9fcb69bcfdb8717bfd0398c6ec2d93036ef8de60" integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q== -marked-terminal@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/marked-terminal/-/marked-terminal-3.3.0.tgz" - integrity sha512-+IUQJ5VlZoAFsM5MHNT7g3RHSkA3eETqhRCdXv4niUMAKHQ7lb1yvAcuGPmm4soxhmtX13u4Li6ZToXtvSEH+A== - dependencies: - ansi-escapes "^3.1.0" - cardinal "^2.1.1" - chalk "^2.4.1" - cli-table "^0.3.1" - node-emoji "^1.4.1" - supports-hyperlinks "^1.0.1" - -marked-terminal@^5.1.1: - version "5.2.0" - resolved "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.2.0.tgz" - integrity sha512-Piv6yNwAQXGFjZSaiNljyNFw7jKDdGrw70FSbtxEyldLsyeuV5ZHm/1wW++kWbrOF1VPnUgYOhB2oLL0ZpnekA== - dependencies: - ansi-escapes "^6.2.0" - cardinal "^2.1.1" - chalk "^5.2.0" - cli-table3 "^0.6.3" - node-emoji "^1.11.0" - supports-hyperlinks "^2.3.0" - -marked@^0.7.0: - version "0.7.0" - resolved "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz" - integrity sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg== - marked@^1.1.1: version "1.2.9" resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.9.tgz#53786f8b05d4c01a2a5a76b7d1ec9943d29d72dc" integrity sha512-H8lIX2SvyitGX+TRdtS06m1jHMijKN/XjfH6Ooii9fvxMlh8QdqBfBDkGUpMWH2kQNrtixjzYUa3SH8ROTgRRw== -marked@^4.0.10, marked@^4.0.14: - version "4.3.0" - resolved "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz" - integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== - math-random@^1.0.1: version "1.0.4" resolved "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz" @@ -18625,11 +16175,6 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -mdurl@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz" - integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" @@ -18680,20 +16225,6 @@ memdown@~3.0.0: ltgt "~2.2.0" safe-buffer "~5.1.1" -memoizee@^0.4.14: - version "0.4.15" - resolved "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz" - integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== - dependencies: - d "^1.0.1" - es5-ext "^0.10.53" - es6-weak-map "^2.0.3" - event-emitter "^0.3.5" - is-promise "^2.2.2" - lru-queue "^0.1.0" - next-tick "^1.1.0" - timers-ext "^0.1.7" - memory-level@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/memory-level/-/memory-level-1.0.0.tgz" @@ -18767,7 +16298,7 @@ merkle-patricia-tree@^2.1.2, merkle-patricia-tree@^2.3.2: rlp "^2.0.0" semaphore ">=1.0.1" -methods@^1.1.2, methods@~1.1.2: +methods@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== @@ -18852,7 +16383,7 @@ mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.16, mime-types@^2.1.35, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.16, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -18864,16 +16395,11 @@ mime@1.6.0: resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@2.6.0, mime@^2.2.0, mime@^2.5.2: +mime@^2.2.0: version "2.6.0" resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== -mime@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz" - integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== - mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz" @@ -18954,20 +16480,13 @@ minimatch@5.0.1: dependencies: brace-expansion "^2.0.1" -minimatch@^5.0.1, minimatch@^5.1.0: +minimatch@^5.0.1: version "5.1.6" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" -minimatch@^6.1.6: - version "6.2.0" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz" - integrity sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg== - dependencies: - brace-expansion "^2.0.1" - minimatch@^9.0.1: version "9.0.3" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" @@ -19001,17 +16520,6 @@ minipass-collect@^1.0.2: dependencies: minipass "^3.0.0" -minipass-fetch@^1.3.2: - version "1.4.1" - resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz" - integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== - dependencies: - minipass "^3.1.0" - minipass-sized "^1.0.3" - minizlib "^2.0.0" - optionalDependencies: - encoding "^0.1.12" - minipass-fetch@^2.0.3: version "2.1.2" resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz" @@ -19049,7 +16557,7 @@ minipass-json-stream@^1.0.1: jsonparse "^1.3.1" minipass "^3.0.0" -minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: +minipass-pipeline@^1.2.4: version "1.2.4" resolved "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz" integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== @@ -19071,7 +16579,7 @@ minipass@^2.6.0, minipass@^2.9.0: safe-buffer "^5.1.2" yallist "^3.0.0" -minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3, minipass@^3.1.6: +minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: version "3.3.6" resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== @@ -19095,7 +16603,7 @@ minizlib@^1.3.3: dependencies: minipass "^2.9.0" -minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2: +minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== @@ -19152,13 +16660,6 @@ mkdirp@0.5.1: dependencies: minimist "0.0.8" -mkdirp@0.5.4: - version "0.5.4" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz" - integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== - dependencies: - minimist "^1.2.5" - mkdirp@0.5.5: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -19166,7 +16667,7 @@ mkdirp@0.5.5: dependencies: minimist "^1.2.5" -mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@^0.5.6, mkdirp@~0.5.1: +mkdirp@0.5.x, mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.6" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -19280,35 +16781,6 @@ mocha@^5.2.0: mkdirp "0.5.1" supports-color "5.4.0" -mocha@^6.2.2: - version "6.2.3" - resolved "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz" - integrity sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg== - dependencies: - ansi-colors "3.2.3" - browser-stdout "1.3.1" - debug "3.2.6" - diff "3.5.0" - escape-string-regexp "1.0.5" - find-up "3.0.0" - glob "7.1.3" - growl "1.10.5" - he "1.2.0" - js-yaml "3.13.1" - log-symbols "2.2.0" - minimatch "3.0.4" - mkdirp "0.5.4" - ms "2.1.1" - node-environment-flags "1.0.5" - object.assign "4.1.0" - strip-json-comments "2.0.1" - supports-color "6.0.0" - which "1.3.1" - wide-align "1.1.3" - yargs "13.3.2" - yargs-parser "13.1.2" - yargs-unparser "1.6.0" - mocha@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.2.0.tgz#01cc227b00d875ab1eed03a75106689cfed5a604" @@ -19377,17 +16849,6 @@ moment@^2.10.6, moment@^2.22.1, moment@^2.29.0: resolved "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz" integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== -morgan@^1.10.0, morgan@^1.8.2: - version "1.10.0" - resolved "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz" - integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ== - dependencies: - basic-auth "~2.0.1" - debug "2.6.9" - depd "~2.0.0" - on-finished "~2.3.0" - on-headers "~1.0.2" - mri@^1.1.4: version "1.2.0" resolved "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz" @@ -19420,15 +16881,6 @@ msal@^1.0.2: dependencies: tslib "^1.9.3" -mssql@^6.3.1: - version "6.4.1" - resolved "https://registry.npmjs.org/mssql/-/mssql-6.4.1.tgz" - integrity sha512-G1I7mM0gfxcH5TGSNoVmxq13Mve5YnQgRAlonqaMlHEjHjMn1g04bsrIQbVHFRdI6++dw/FGWlh8GoItJMoUDw== - dependencies: - debug "^4.3.3" - tarn "^1.1.5" - tedious "^6.7.1" - multi-progress@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/multi-progress/-/multi-progress-2.0.0.tgz" @@ -19517,20 +16969,6 @@ mv@~2: ncp "~2.0.0" rimraf "~2.4.0" -mysql2@^2.1.0: - version "2.3.3" - resolved "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz" - integrity sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA== - dependencies: - denque "^2.0.1" - generate-function "^2.3.1" - iconv-lite "^0.6.3" - long "^4.0.0" - lru-cache "^6.0.0" - named-placeholders "^1.1.2" - seq-queue "^0.0.5" - sqlstring "^2.3.2" - mythxjs@^1.3.11: version "1.3.13" resolved "https://registry.npmjs.org/mythxjs/-/mythxjs-1.3.13.tgz" @@ -19549,19 +16987,12 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -named-placeholders@^1.1.2: - version "1.1.3" - resolved "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz" - integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w== - dependencies: - lru-cache "^7.14.1" - nan@^2.13.2, nan@^2.14.0: version "2.17.0" resolved "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz" integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== -nan@^2.17.0, nan@^2.2.1: +nan@^2.2.1: version "2.18.0" resolved "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz" integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== @@ -19613,21 +17044,6 @@ napi-macros@~2.0.0: resolved "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz" integrity sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg== -nash@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/nash/-/nash-3.0.0.tgz" - integrity sha512-M5SahEycXUmko3zOvsBkF6p94CWLhnyy9hfpQ9Qzp+rQkQ8D1OaTlfTl1OBWktq9Fak3oDXKU+ev7tiMaMu+1w== - dependencies: - async "^1.3.0" - flat-arguments "^1.0.0" - lodash "^4.17.5" - minimist "^1.1.0" - -native-duplexpair@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz" - integrity sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" @@ -19643,7 +17059,7 @@ ncp@~2.0.0: resolved "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz" integrity sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA== -negotiator@0.6.3, negotiator@^0.6.2, negotiator@^0.6.3: +negotiator@0.6.3, negotiator@^0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== @@ -19660,12 +17076,7 @@ neodoc@^2.0.2: dependencies: ansi-regex "^2.0.0" -netmask@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz" - integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== - -next-tick@1, next-tick@^1.1.0: +next-tick@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== @@ -19716,21 +17127,6 @@ node-addon-api@^4.2.0, node-addon-api@^4.3.0: resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== -node-emoji@^1.11.0, node-emoji@^1.4.1: - version "1.11.0" - resolved "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz" - integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== - dependencies: - lodash "^4.17.21" - -node-environment-flags@1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz" - integrity sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ== - dependencies: - object.getownpropertydescriptors "^2.0.3" - semver "^5.7.0" - node-environment-flags@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" @@ -19739,11 +17135,6 @@ node-environment-flags@1.0.6: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-fetch@2.6.0: - version "2.6.0" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz" - integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== - node-fetch@2.6.1: version "2.6.1" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz" @@ -19808,23 +17199,7 @@ node-gyp-build@~4.1.0: resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz" integrity sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ== -node-gyp@8.x: - version "8.4.1" - resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz" - integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.6" - make-fetch-happen "^9.1.0" - nopt "^5.0.0" - npmlog "^6.0.0" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.2" - which "^2.0.2" - -node-gyp@^9.0.0, node-gyp@^9.4.0: +node-gyp@^9.0.0: version "9.4.0" resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.0.tgz" integrity sha512-dMXsYP6gc9rRbejLXmTbVRYjAHw7ppswsKyMxuxJxxOHzluIO1rGp9TOQgjFJ+2MCqcOcQTOPB/8Xwhr+7s4Eg== @@ -20113,11 +17488,6 @@ number-to-bn@1.7.0: bn.js "4.11.6" strip-hex-prefix "1.0.0" -numeral@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz" - integrity sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA== - numeric@^1.2.6: version "1.2.6" resolved "https://registry.npmjs.org/numeric/-/numeric-1.2.6.tgz" @@ -20329,24 +17699,6 @@ obuf@~1.1.2: resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -"old-identity-sdk@npm:@celo/identity@1.5.2": - version "1.5.2" - resolved "https://registry.npmjs.org/@celo/identity/-/identity-1.5.2.tgz" - integrity sha512-/9JTL5P4xTY37hgu6qh5tU1d2GS3duBjP3QL600Zz1KAQrUVgb8g8JPpiRY21oEK6L7ZoNTukQJIuM3sbi//vg== - dependencies: - "@celo/base" "1.5.2" - "@celo/contractkit" "1.5.2" - "@celo/phone-number-privacy-common" "1.0.39" - "@celo/utils" "1.5.2" - "@types/debug" "^4.1.5" - bignumber.js "^9.0.0" - blind-threshold-bls "https://github.com/celo-org/blind-threshold-bls-wasm#e1e2f8a" - cross-fetch "3.0.4" - debug "^4.1.1" - elliptic "^6.5.4" - fp-ts "2.1.1" - io-ts "2.0.1" - omni-fetch@^0.2.3: version "0.2.3" resolved "https://registry.npmjs.org/omni-fetch/-/omni-fetch-0.2.3.tgz" @@ -20356,25 +17708,13 @@ omni-fetch@^0.2.3: "@types/node" "^6.0.52" caw "^1.2.0" -on-finished@2.4.1, on-finished@^2.2.0: +on-finished@2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== dependencies: ee-first "1.1.1" -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" - integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== - dependencies: - ee-first "1.1.1" - -on-headers@^1.0.0, on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - once@1.x, once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" @@ -20382,13 +17722,6 @@ once@1.x, once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -one-time@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz" - integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== - dependencies: - fn.name "1.x.x" - onetime@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz" @@ -20403,13 +17736,6 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -open@^6.3.0: - version "6.4.0" - resolved "https://registry.npmjs.org/open/-/open-6.4.0.tgz" - integrity sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg== - dependencies: - is-wsl "^1.1.0" - open@^7.0.0, open@^7.4.2: version "7.4.2" resolved "https://registry.npmjs.org/open/-/open-7.4.2.tgz" @@ -20427,20 +17753,6 @@ open@^8.4.0: is-docker "^2.1.1" is-wsl "^2.2.0" -openapi3-ts@^2.0.1: - version "2.0.2" - resolved "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-2.0.2.tgz" - integrity sha512-TxhYBMoqx9frXyOgnRHufjQfPXomTIHYKhSKJ6jHfj13kS8OEIhvmE8CTuQyKtjjWttAjX5DPxM1vmalEpo8Qw== - dependencies: - yaml "^1.10.2" - -openapi3-ts@^3.1.1: - version "3.2.0" - resolved "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-3.2.0.tgz" - integrity sha512-/ykNWRV5Qs0Nwq7Pc0nJ78fgILvOT/60OxEmB3v7yQ8a8Bwcm43D4diaYazG/KBn6czA+52XYy931WFLMCUeSg== - dependencies: - yaml "^2.2.1" - opencollective-postinstall@^2.0.2: version "2.0.3" resolved "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz" @@ -20556,11 +17868,6 @@ p-defer@^1.0.0: resolved "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz" integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw== -p-defer@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz" - integrity sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw== - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" @@ -20590,7 +17897,7 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.1, p-limit@^3.0.2, p-limit@^3.1.0: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -20686,63 +17993,6 @@ p-waterfall@^2.1.1: dependencies: p-reduce "^2.0.0" -pac-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-5.0.0.tgz" - integrity sha512-CcFG3ZtnxO8McDigozwE3AqAw15zDvGH+OjXO4kzf7IkEKkQ4gxQ+3sdF50WmhQ4P/bVusXcqNE2S3XrNURwzQ== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - get-uri "3" - http-proxy-agent "^4.0.1" - https-proxy-agent "5" - pac-resolver "^5.0.0" - raw-body "^2.2.0" - socks-proxy-agent "5" - -pac-proxy-agent@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz" - integrity sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A== - dependencies: - "@tootallnate/quickjs-emscripten" "^0.23.0" - agent-base "^7.0.2" - debug "^4.3.4" - get-uri "^6.0.1" - http-proxy-agent "^7.0.0" - https-proxy-agent "^7.0.2" - pac-resolver "^7.0.0" - socks-proxy-agent "^8.0.2" - -pac-resolver@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/pac-resolver/-/pac-resolver-5.0.1.tgz" - integrity sha512-cy7u00ko2KVgBAjuhevqpPeHIkCIqPe1v24cydhWjmeuzaBfmUWFCZJ1iAh5TuVzVZoUzXIW7K8sMYOZ84uZ9Q== - dependencies: - degenerator "^3.0.2" - ip "^1.1.5" - netmask "^2.0.2" - -pac-resolver@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz" - integrity sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg== - dependencies: - degenerator "^5.0.0" - ip "^1.1.8" - netmask "^2.0.2" - -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - packet-reader@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz" @@ -21023,13 +18273,6 @@ path-to-regexp@0.1.7: resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== -path-to-regexp@^1.8.0: - version "1.8.0" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - path-to-regexp@^2.2.1: version "2.4.0" resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz" @@ -21090,26 +18333,11 @@ performance-now@^2.1.0: resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -pg-cloudflare@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz" - integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== - pg-connection-string@0.1.3: version "0.1.3" resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz" integrity sha512-i0NV/CrSkFTaiOQs9AGy3tq0dkSjtTd4d7DfsjeDVZAA4aIHInwfFEmriNYGGJUfZ5x6IAC/QddoUpUJjQAi0w== -pg-connection-string@2.6.1: - version "2.6.1" - resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz" - integrity sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg== - -pg-connection-string@^2.6.2: - version "2.6.2" - resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz" - integrity sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA== - pg-int8@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz" @@ -21130,12 +18358,7 @@ pg-pool@^2.0.10: resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.10.tgz" integrity sha512-qdwzY92bHf3nwzIUcj+zJ0Qo5lpG/YxchahxIN8+ZVmXqkahKXsnl2aiJPHLYN9o5mB/leG+Xh6XKxtP7e0sjg== -pg-pool@^3.6.1: - version "3.6.1" - resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz" - integrity sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og== - -pg-protocol@*, pg-protocol@^1.2.0, pg-protocol@^1.6.0: +pg-protocol@*, pg-protocol@^1.2.0: version "1.6.0" resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz" integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== @@ -21178,21 +18401,6 @@ pg@^7.18.0: pgpass "1.x" semver "4.3.2" -pg@^8.2.1: - version "8.11.3" - resolved "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz" - integrity sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g== - dependencies: - buffer-writer "2.0.0" - packet-reader "1.0.0" - pg-connection-string "^2.6.2" - pg-pool "^3.6.1" - pg-protocol "^1.6.0" - pg-types "^2.1.0" - pgpass "1.x" - optionalDependencies: - pg-cloudflare "^1.1.1" - pgpass@1.x: version "1.0.5" resolved "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz" @@ -21295,15 +18503,6 @@ popper.js@1.14.3: resolved "https://registry.npmjs.org/popper.js/-/popper.js-1.14.3.tgz" integrity sha512-3lmujhsHXzb83+sI0PzfrE3O1XHZG8m8MXNMTupvA6LrM1/nnsiqYaacYc/RIente9VqnTDPztGEM8uvPAMGyg== -portfinder@^1.0.23, portfinder@^1.0.32: - version "1.0.32" - resolved "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz" - integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg== - dependencies: - async "^2.6.4" - debug "^3.2.7" - mkdirp "^0.5.6" - posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -21688,11 +18887,6 @@ proc-log@^2.0.0, proc-log@^2.0.1: resolved "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz" integrity sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw== -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" - integrity sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw== - process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" @@ -21718,28 +18912,11 @@ progress@^2.0.0, progress@^2.0.3: resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -prom-client@12.0.0: - version "12.0.0" - resolved "https://registry.npmjs.org/prom-client/-/prom-client-12.0.0.tgz" - integrity sha512-JbzzHnw0VDwCvoqf8y1WDtq4wSBAbthMB1pcVI/0lzdqHGJI3KBJDXle70XK+c7Iv93Gihqo0a5LlOn+g8+DrQ== - dependencies: - tdigest "^0.1.1" - promise-all-reject-late@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz" integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw== -promise-breaker@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/promise-breaker/-/promise-breaker-5.0.0.tgz" - integrity sha512-mgsWQuG4kJ1dtO6e/QlNDLFtMkMzzecsC69aI5hlLEjGHFNpHrvGhFi4LiK5jg2SMQj74/diH+wZliL9LpGsyA== - -promise-breaker@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/promise-breaker/-/promise-breaker-6.0.0.tgz" - integrity sha512-BthzO9yTPswGf7etOBiHCVuugs2N01/Q/94dIPls48z2zCmrnDptUUZzfIb+41xq0MnYZ/BzmOd6ikDR4ibNZA== - promise-call-limit@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.2.tgz" @@ -21777,18 +18954,6 @@ promise.allsettled@1.0.2: function-bind "^1.1.1" iterate-value "^1.0.0" -promise.allsettled@^1.0.2: - version "1.0.7" - resolved "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.7.tgz" - integrity sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA== - dependencies: - array.prototype.map "^1.0.5" - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" - iterate-value "^1.0.2" - promise@^8.0.0: version "8.3.0" resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" @@ -21842,48 +19007,6 @@ proto3-json-serializer@^0.1.8: dependencies: protobufjs "^6.11.2" -proto3-json-serializer@^1.0.0: - version "1.1.1" - resolved "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz" - integrity sha512-AwAuY4g9nxx0u52DnSMkqqgyLHaW/XaPLtaAo3y/ZCfeaQB/g4YDH4kb8Wc/mWzWvu0YjOznVnfn373MVZZrgw== - dependencies: - protobufjs "^7.0.0" - -protobufjs-cli@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.1.tgz" - integrity sha512-VPWMgIcRNyQwWUv8OLPyGQ/0lQY/QTQAVN5fh+XzfDwsVw1FZ2L3DM/bcBf8WPiRz2tNpaov9lPZfNcmNo6LXA== - dependencies: - chalk "^4.0.0" - escodegen "^1.13.0" - espree "^9.0.0" - estraverse "^5.1.0" - glob "^8.0.0" - jsdoc "^4.0.0" - minimist "^1.2.0" - semver "^7.1.2" - tmp "^0.2.1" - uglify-js "^3.7.7" - -protobufjs@6.11.2: - version "6.11.2" - resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz" - integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.1" - "@types/node" ">=13.7.0" - long "^4.0.0" - protobufjs@6.11.3: version "6.11.3" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz" @@ -21903,24 +19026,6 @@ protobufjs@6.11.3: "@types/node" ">=13.7.0" long "^4.0.0" -protobufjs@7.2.4, protobufjs@^7.2.3: - version "7.2.4" - resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz" - integrity sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/node" ">=13.7.0" - long "^5.0.0" - protobufjs@^5.0.3: version "5.0.3" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz" @@ -21931,7 +19036,7 @@ protobufjs@^5.0.3: glob "^7.0.5" yargs "^3.10.0" -protobufjs@^6.10.0, protobufjs@^6.11.2, protobufjs@^6.11.3, protobufjs@^6.8.0, protobufjs@^6.8.1, protobufjs@^6.8.6, protobufjs@^6.8.8: +protobufjs@^6.11.2, protobufjs@^6.11.3, protobufjs@^6.8.0, protobufjs@^6.8.1, protobufjs@^6.8.6, protobufjs@^6.8.8: version "6.11.4" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz" integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw== @@ -21968,10 +19073,10 @@ protobufjs@^7.0.0: "@types/node" ">=13.7.0" long "^5.0.0" -protobufjs@^7.2.2: - version "7.2.5" - resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz" - integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A== +protobufjs@^7.2.3: + version "7.2.4" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz" + integrity sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -21999,35 +19104,7 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/proxy-agent/-/proxy-agent-5.0.0.tgz" - integrity sha512-gkH7BkvLVkSfX9Dk27W6TyNOWWZWRilRfk1XxGNWOYJ2TuedAv1yFpCaU9QSBmBe716XOTNpYNOzhysyw8xn7g== - dependencies: - agent-base "^6.0.0" - debug "4" - http-proxy-agent "^4.0.0" - https-proxy-agent "^5.0.0" - lru-cache "^5.1.1" - pac-proxy-agent "^5.0.0" - proxy-from-env "^1.0.0" - socks-proxy-agent "^5.0.0" - -proxy-agent@^6.3.0: - version "6.3.1" - resolved "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz" - integrity sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ== - dependencies: - agent-base "^7.0.2" - debug "^4.3.4" - http-proxy-agent "^7.0.0" - https-proxy-agent "^7.0.2" - lru-cache "^7.14.1" - pac-proxy-agent "^7.0.1" - proxy-from-env "^1.1.0" - socks-proxy-agent "^8.0.2" - -proxy-from-env@^1.0.0, proxy-from-env@^1.1.0: +proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== @@ -22046,11 +19123,6 @@ prr@~1.0.1: resolved "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz" integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== -pseudomap@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz" - integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== - psl@^1.1.28, psl@^1.1.33: version "1.9.0" resolved "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz" @@ -22101,15 +19173,6 @@ pumpify@^1.5.1: inherits "^2.0.3" pump "^2.0.0" -pumpify@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz" - integrity sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw== - dependencies: - duplexify "^4.1.1" - inherits "^2.0.3" - pump "^3.0.0" - punycode@1.3.2: version "1.3.2" resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" @@ -22130,13 +19193,6 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== -pupa@^2.0.1, pupa@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz" - integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== - dependencies: - escape-goat "^2.0.0" - pure-rand@^5.0.1: version "5.0.5" resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-5.0.5.tgz" @@ -22196,7 +19252,7 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" -qs@^6.11.0, qs@^6.4.0, qs@^6.5.2, qs@^6.6.0: +qs@^6.4.0, qs@^6.5.2: version "6.11.2" resolved "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz" integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== @@ -22298,7 +19354,7 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" -raw-body@2.5.2, raw-body@^2.2.0, raw-body@^2.3.3: +raw-body@2.5.2: version "2.5.2" resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz" integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== @@ -22308,7 +19364,7 @@ raw-body@2.5.2, raw-body@^2.2.0, raw-body@^2.3.3: iconv-lite "0.4.24" unpipe "1.0.0" -rc@1.2.8, rc@^1.1.2, rc@^1.2.7, rc@^1.2.8: +rc@^1.1.2, rc@^1.2.7: version "1.2.8" resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -22318,15 +19374,6 @@ rc@1.2.8, rc@^1.1.2, rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -re2@^1.15.8, re2@^1.17.7: - version "1.20.3" - resolved "https://registry.npmjs.org/re2/-/re2-1.20.3.tgz" - integrity sha512-g5j4YjygwGEccP9SCuDI90uPlgALLEYLotfL0K+kqL3XKB4ht7Nm1JuXfOTG96c7JozpvCUxTz1T7oTNwwMI6w== - dependencies: - install-artifact-from-github "^1.3.3" - nan "^2.17.0" - node-gyp "^9.4.0" - react-is@^18.0.0: version "18.2.0" resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" @@ -22422,7 +19469,7 @@ read@1, read@1.0.x, read@^1.0.7: dependencies: mute-stream "~0.0.4" -readable-stream@1.1.14, readable-stream@1.1.x, readable-stream@^1.0.33: +readable-stream@1.1.14, readable-stream@^1.0.33: version "1.1.14" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" integrity sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ== @@ -22432,7 +19479,7 @@ readable-stream@1.1.14, readable-stream@1.1.x, readable-stream@^1.0.33: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.1, readable-stream@^3.0.2, readable-stream@^3.1.0, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.0, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -22441,7 +19488,7 @@ readable-stream@1.1.14, readable-stream@1.1.x, readable-stream@^1.0.33: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -22469,25 +19516,6 @@ readable-stream@~1.0.15: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@~2.0.0: - version "2.0.6" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" - integrity sha512-TXcFfb63BQe1+ySzsHZI/5v1aJPCShfqvWJ64ayNImXMsN1Cd0YGk/wm8KB7/OeessgPc9QvS9Zou8QTkFzsLw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - -readdir-glob@^1.1.2: - version "1.1.3" - resolved "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz" - integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== - dependencies: - minimatch "^5.1.0" - readdir-scoped-modules@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz" @@ -22526,13 +19554,6 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" -rechoir@^0.8.0: - version "0.8.0" - resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz" - integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== - dependencies: - resolve "^1.20.0" - redent@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz" @@ -22630,27 +19651,6 @@ regexpp@^2.0.1: resolved "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz" integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== -registry-auth-token@^4.0.0: - version "4.2.2" - resolved "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz" - integrity sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg== - dependencies: - rc "1.2.8" - -registry-auth-token@^5.0.1: - version "5.0.2" - resolved "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz" - integrity sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ== - dependencies: - "@pnpm/npm-conf" "^2.1.0" - -registry-url@^5.0.0, registry-url@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - relative@^3.0.1: version "3.0.2" resolved "https://registry.npmjs.org/relative/-/relative-3.0.2.tgz" @@ -22728,7 +19728,7 @@ request-promise@^4.2.2: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.67.0, request@^2.79.0, request@^2.85.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: +request@^2.67.0, request@^2.79.0, request@^2.85.0, request@^2.88.0: version "2.88.2" resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -22793,13 +19793,6 @@ requires-port@^1.0.0: resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== -requizzle@^0.2.3: - version "0.2.4" - resolved "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz" - integrity sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw== - dependencies: - lodash "^4.17.21" - reselect-tree@^1.3.7: version "1.3.7" resolved "https://registry.npmjs.org/reselect-tree/-/reselect-tree-1.3.7.tgz" @@ -22932,7 +19925,7 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retry-request@^4.0.0, retry-request@^4.2.2: +retry-request@^4.0.0: version "4.2.2" resolved "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz" integrity sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg== @@ -22940,15 +19933,7 @@ retry-request@^4.0.0, retry-request@^4.2.2: debug "^4.1.1" extend "^3.0.2" -retry-request@^5.0.0: - version "5.0.2" - resolved "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz" - integrity sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ== - dependencies: - debug "^4.1.1" - extend "^3.0.2" - -retry@0.13.1, retry@^0.13.1: +retry@0.13.1: version "0.13.1" resolved "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz" integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== @@ -22968,13 +19953,6 @@ revalidator@0.1.x: resolved "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz" integrity sha512-xcBILK2pA9oh4SiinPEZfhP8HfrB/ha+a2fTMyl7Om2WjlDVrOQy99N2MXXlUHqGJz4qEu2duXxHJjDWuK/0xg== -rimraf@2, rimraf@^2.2.8, rimraf@^2.6.2, rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - rimraf@2.6.3: version "2.6.3" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz" @@ -22989,6 +19967,13 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rimraf@^2.2.8, rimraf@^2.6.2, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@~2.4.0: version "2.4.5" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz" @@ -23016,24 +20001,6 @@ rlp@^2.0.0, rlp@^2.2.1, rlp@^2.2.2, rlp@^2.2.3, rlp@^2.2.4: dependencies: bn.js "^5.2.0" -router@^1.3.1: - version "1.3.8" - resolved "https://registry.npmjs.org/router/-/router-1.3.8.tgz" - integrity sha512-461UFH44NtSfIlS83PUg2N7OZo86BC/kB3dY77gJdsODsBhhw7+2uE0tzTINxrY9CahCUVk1VhpWCA5i1yoIEg== - dependencies: - array-flatten "3.0.0" - debug "2.6.9" - methods "~1.1.2" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - setprototypeof "1.2.0" - utils-merge "1.0.1" - -rsvp@^4.8.5: - version "4.8.5" - resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz" - integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== - run-async@^2.2.0, run-async@^2.4.0: version "2.4.1" resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" @@ -23087,16 +20054,16 @@ safe-array-concat@^1.0.0: has-symbols "^1.0.3" isarray "^2.0.5" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + safe-event-emitter@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/safe-event-emitter/-/safe-event-emitter-1.0.1.tgz" @@ -23125,11 +20092,6 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -safe-stable-stringify@^2.3.1: - version "2.4.3" - resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz" - integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== - "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" @@ -23234,13 +20196,6 @@ semver-compare@^1.0.0: resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz" integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.7.1" resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" @@ -23277,7 +20232,7 @@ semver@7.3.7: dependencies: lru-cache "^6.0.0" -semver@^5.0.1, semver@^5.7.0, semver@^5.7.1: +semver@^5.7.0: version "5.7.2" resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -23287,12 +20242,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^6.2.0: - version "6.3.1" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.0.0, semver@^7.1.1, semver@^7.1.2, semver@^7.3.4, semver@^7.3.7, semver@^7.5.1, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4: +semver@^7.0.0, semver@^7.1.1, semver@^7.3.4, semver@^7.3.7, semver@^7.5.1, semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -23338,11 +20288,6 @@ sentence-case@^2.1.0: no-case "^2.2.0" upper-case-first "^1.1.2" -seq-queue@^0.0.5: - version "0.0.5" - resolved "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz" - integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q== - serialize-javascript@3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz" @@ -23417,7 +20362,7 @@ setimmediate@1.0.4: resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz" integrity sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog== -setimmediate@^1.0.5, setimmediate@~1.0.4: +setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== @@ -23551,13 +20496,6 @@ simple-get@^4.0.0: once "^1.3.1" simple-concat "^1.0.0" -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - sisteransi@^1.0.0, sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" @@ -23646,24 +20584,6 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -socks-proxy-agent@5, socks-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz" - integrity sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ== - dependencies: - agent-base "^6.0.2" - debug "4" - socks "^2.3.3" - -socks-proxy-agent@^6.0.0: - version "6.2.1" - resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz" - integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== - dependencies: - agent-base "^6.0.2" - debug "^4.3.3" - socks "^2.6.2" - socks-proxy-agent@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz" @@ -23673,16 +20593,7 @@ socks-proxy-agent@^7.0.0: debug "^4.3.3" socks "^2.6.2" -socks-proxy-agent@^8.0.2: - version "8.0.2" - resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz" - integrity sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g== - dependencies: - agent-base "^7.0.2" - debug "^4.3.4" - socks "^2.7.1" - -socks@^2.3.3, socks@^2.6.2, socks@^2.7.1: +socks@^2.6.2: version "2.7.1" resolved "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz" integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== @@ -23844,7 +20755,7 @@ source-map@^0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: +source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -23929,32 +20840,11 @@ split@^1.0.0: dependencies: through "2" -sprintf-js@^1.1.2: - version "1.1.3" - resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -sqlite3@^5.0.8: - version "5.1.6" - resolved "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.6.tgz" - integrity sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw== - dependencies: - "@mapbox/node-pre-gyp" "^1.0.0" - node-addon-api "^4.2.0" - tar "^6.1.11" - optionalDependencies: - node-gyp "8.x" - -sqlstring@^2.3.2: - version "2.3.3" - resolved "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz" - integrity sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg== - sshpk@^1.7.0: version "1.17.0" resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz" @@ -23977,13 +20867,6 @@ ssri@^10.0.0: dependencies: minipass "^7.0.3" -ssri@^8.0.0, ssri@^8.0.1: - version "8.0.1" - resolved "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz" - integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== - dependencies: - minipass "^3.1.1" - ssri@^9.0.0, ssri@^9.0.1: version "9.0.1" resolved "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz" @@ -24021,7 +20904,7 @@ statuses@2.0.1: resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: +"statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== @@ -24043,11 +20926,6 @@ stoppable@^1.1.0: resolved "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz" integrity sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw== -stream-chain@^2.2.4, stream-chain@^2.2.5: - version "2.2.5" - resolved "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz" - integrity sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA== - stream-events@^1.0.1, stream-events@^1.0.4, stream-events@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz" @@ -24055,23 +20933,11 @@ stream-events@^1.0.1, stream-events@^1.0.4, stream-events@^1.0.5: dependencies: stubs "^3.0.0" -stream-json@^1.7.3: - version "1.8.0" - resolved "https://registry.npmjs.org/stream-json/-/stream-json-1.8.0.tgz" - integrity sha512-HZfXngYHUAr1exT4fxlbc1IOce1RYxp2ldeaf97LYCOPSoOqY/1Psp7iGvpb+6JIOgkra9zDYnPX01hGAHzEPw== - dependencies: - stream-chain "^2.2.5" - stream-shift@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -streamsearch@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" - integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== - strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz" @@ -24082,13 +20948,6 @@ string-hash@^1.1.3: resolved "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz" integrity sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A== -string-length@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz" - integrity sha512-MNCACnufWUf3pQ57O5WTBMkKhzYIaKEcUioO0XHrTMafrbBaNk4IyDOLHBv5xbXO0jLLdsYWeFjpjG2hVHRDtw== - dependencies: - strip-ansi "^3.0.0" - string-length@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" @@ -24102,7 +20961,7 @@ string-template@~0.2.1: resolved "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz" integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -24295,16 +21154,11 @@ strip-json-comments@3.0.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz" integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== -strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: +strip-json-comments@3.1.1, strip-json-comments@^3.1.1: 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== -strnum@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz" - integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== - strong-log-transformer@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz" @@ -24314,102 +21168,20 @@ strong-log-transformer@^2.1.0: minimist "^1.2.0" through "^2.3.4" -stubs@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz" - integrity sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw== - -sublevel-pouchdb@7.3.1: - version "7.3.1" - resolved "https://registry.npmjs.org/sublevel-pouchdb/-/sublevel-pouchdb-7.3.1.tgz" - integrity sha512-n+4fK72F/ORdqPwoGgMGYeOrW2HaPpW9o9k80bT1B3Cim5BSvkKkr9WbWOWynni/GHkbCEdvLVFJL1ktosAdhQ== - dependencies: - inherits "2.0.4" - level-codec "9.0.2" - ltgt "2.2.1" - readable-stream "1.1.14" - -superagent@^8.0.5: - version "8.1.2" - resolved "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz" - integrity sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA== - dependencies: - component-emitter "^1.3.0" - cookiejar "^2.1.4" - debug "^4.3.4" - fast-safe-stringify "^2.1.1" - form-data "^4.0.0" - formidable "^2.1.2" - methods "^1.1.2" - mime "2.6.0" - qs "^6.11.0" - semver "^7.3.8" - -superstatic@^7.1.0: - version "7.1.0" - resolved "https://registry.npmjs.org/superstatic/-/superstatic-7.1.0.tgz" - integrity sha512-yBU8iw07nM3Bu4jFc8lnKwLey0cj61OaGmFJZcYC2X+kEpXVmXzERJ3OTAHZAESe1OTeNIuWadt81U5IULGGAA== - dependencies: - basic-auth-connect "^1.0.0" - chalk "^1.1.3" - compare-semver "^1.0.0" - compression "^1.7.0" - connect "^3.6.2" - destroy "^1.0.4" - fast-url-parser "^1.1.3" - fs-extra "^8.1.0" - glob-slasher "^1.0.1" - home-dir "^1.0.0" - is-url "^1.2.2" - join-path "^1.1.1" - lodash "^4.17.19" - mime-types "^2.1.16" - minimatch "^3.0.4" - morgan "^1.8.2" - nash "^3.0.0" - on-finished "^2.2.0" - on-headers "^1.0.0" - path-to-regexp "^1.8.0" - router "^1.3.1" - rsvp "^4.8.5" - string-length "^1.0.0" - update-notifier "^4.1.1" - optionalDependencies: - re2 "^1.15.8" - -superstatic@^9.0.3: - version "9.0.3" - resolved "https://registry.npmjs.org/superstatic/-/superstatic-9.0.3.tgz" - integrity sha512-e/tmW0bsnQ/33ivK6y3CapJT0Ovy4pk/ohNPGhIAGU2oasoNLRQ1cv6enua09NU9w6Y0H/fBu07cjzuiWvLXxw== - dependencies: - basic-auth-connect "^1.0.0" - commander "^10.0.0" - compression "^1.7.0" - connect "^3.7.0" - destroy "^1.0.4" - fast-url-parser "^1.1.3" - glob-slasher "^1.0.1" - is-url "^1.2.2" - join-path "^1.1.1" - lodash "^4.17.19" - mime-types "^2.1.35" - minimatch "^6.1.6" - morgan "^1.8.2" - on-finished "^2.2.0" - on-headers "^1.0.0" - path-to-regexp "^1.8.0" - router "^1.3.1" - update-notifier-cjs "^5.1.6" - optionalDependencies: - re2 "^1.17.7" - -supertest@^6.2.3: - version "6.3.3" - resolved "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz" - integrity sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA== +stubs@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz" + integrity sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw== + +sublevel-pouchdb@7.3.1: + version "7.3.1" + resolved "https://registry.npmjs.org/sublevel-pouchdb/-/sublevel-pouchdb-7.3.1.tgz" + integrity sha512-n+4fK72F/ORdqPwoGgMGYeOrW2HaPpW9o9k80bT1B3Cim5BSvkKkr9WbWOWynni/GHkbCEdvLVFJL1ktosAdhQ== dependencies: - methods "^1.1.2" - superagent "^8.0.5" + inherits "2.0.4" + level-codec "9.0.2" + ltgt "2.2.1" + readable-stream "1.1.14" supports-color@5.4.0: version "5.4.0" @@ -24439,11 +21211,6 @@ supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.0: dependencies: has-flag "^4.0.0" -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" - integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== - supports-color@^3.1.0: version "3.2.3" resolved "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz" @@ -24473,7 +21240,7 @@ supports-hyperlinks@^1.0.1: has-flag "^2.0.0" supports-color "^5.0.0" -supports-hyperlinks@^2.1.0, supports-hyperlinks@^2.3.0: +supports-hyperlinks@^2.1.0: version "2.3.0" resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz" integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== @@ -24614,7 +21381,7 @@ tar-stream@^1.1.2, tar-stream@^1.5.2: to-buffer "^1.1.1" xtend "^4.0.0" -tar-stream@^2.1.4, tar-stream@^2.2.0, tar-stream@~2.2.0: +tar-stream@^2.1.4, tar-stream@~2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== @@ -24625,7 +21392,7 @@ tar-stream@^2.1.4, tar-stream@^2.2.0, tar-stream@~2.2.0: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4.0.2, tar@^4.3.0: +tar@^4.0.2: version "4.4.19" resolved "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz" integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== @@ -24638,7 +21405,7 @@ tar@^4.0.2, tar@^4.3.0: safe-buffer "^5.2.1" yallist "^3.1.1" -tar@^6.0.2, tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: +tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: version "6.2.0" resolved "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz" integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== @@ -24657,48 +21424,6 @@ targz@^1.0.1: dependencies: tar-fs "^1.8.1" -tarn@^1.1.5: - version "1.1.5" - resolved "https://registry.npmjs.org/tarn/-/tarn-1.1.5.tgz" - integrity sha512-PMtJ3HCLAZeedWjJPgGnCvcphbCOMbtZpjKgLq3qM5Qq9aQud+XHrL0WlrlgnTyS8U+jrjGbEXprFcQrxPy52g== - -tarn@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz" - integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ== - -tcp-port-used@^1.0.1, tcp-port-used@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz" - integrity sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA== - dependencies: - debug "4.3.1" - is2 "^2.0.6" - -tdigest@^0.1.1: - version "0.1.2" - resolved "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz" - integrity sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA== - dependencies: - bintrees "1.0.2" - -tedious@^6.7.1: - version "6.7.1" - resolved "https://registry.npmjs.org/tedious/-/tedious-6.7.1.tgz" - integrity sha512-61eg/mvUa5vIqZcRizcqw/82dY65kR2uTll1TaUFh0aJ45XOrgbc8axiVR48dva8BahIAlJByaHNfAJ/KmPV0g== - dependencies: - "@azure/ms-rest-nodeauth" "^3.0.10" - "@types/node" "^12.12.17" - "@types/readable-stream" "^2.3.5" - bl "^3.0.0" - depd "^2.0.0" - iconv-lite "^0.5.0" - jsbi "^3.1.1" - native-duplexpair "^1.0.0" - punycode "^2.1.0" - readable-stream "^3.4.0" - sprintf-js "^1.1.2" - teeny-request@7.1.1: version "7.1.1" resolved "https://registry.npmjs.org/teeny-request/-/teeny-request-7.1.1.tgz" @@ -24719,38 +21444,11 @@ teeny-request@^3.11.3: node-fetch "^2.2.0" uuid "^3.3.2" -teeny-request@^7.1.3: - version "7.2.0" - resolved "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz" - integrity sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw== - dependencies: - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.0" - node-fetch "^2.6.1" - stream-events "^1.0.5" - uuid "^8.0.0" - -teeny-request@^8.0.0: - version "8.0.3" - resolved "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.3.tgz" - integrity sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww== - dependencies: - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.0" - node-fetch "^2.6.1" - stream-events "^1.0.5" - uuid "^9.0.0" - temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz" integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== -term-size@^2.1.0: - version "2.2.1" - resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz" - integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== - test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" @@ -24773,21 +21471,11 @@ testrpc@0.0.1: resolved "https://registry.npmjs.org/testrpc/-/testrpc-0.0.1.tgz" integrity sha512-afH1hO+SQ/VPlmaLUFj2636QMeDvPCeQMc/9RBMW0IfjNe9gFD9Ra3ShqYkB7py0do1ZcCna/9acHyzTJ+GcNA== -text-decoding@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz" - integrity sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA== - text-extensions@^1.0.0: version "1.9.0" resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz" integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - text-table@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" @@ -24833,14 +21521,6 @@ thriftrw@^3.5.0: error "7.0.2" long "^2.4.0" -through2@2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/through2/-/through2-2.0.1.tgz" - integrity sha512-/vp02SIbpmVHapNMjox4hDBzykPdAOmH5y3INcKaxGfpEPSCMqzdWXyGfqPYyxoBLo1JpxBrlh3Z9esv0vWUYw== - dependencies: - readable-stream "~2.0.0" - xtend "~4.0.0" - through2@3.0.2, through2@^3.0.0, through2@^3.0.1: version "3.0.2" resolved "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz" @@ -24869,24 +21549,11 @@ through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== -tildify@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz" - integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw== - timed-out@^4.0.0, timed-out@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz" integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA== -timers-ext@^0.1.5, timers-ext@^0.1.7: - version "0.1.7" - resolved "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz" - integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== - dependencies: - es5-ext "~0.10.46" - next-tick "1" - tiny-async-pool@^1.0.4: version "1.3.0" resolved "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz" @@ -24906,17 +21573,6 @@ tiny-secp256k1@2.2.1: dependencies: uint8array-tools "0.0.7" -tiny-secp256k1@^1.1.3: - version "1.1.6" - resolved "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz" - integrity sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA== - dependencies: - bindings "^1.3.0" - bn.js "^4.11.8" - create-hmac "^1.1.7" - elliptic "^6.4.0" - nan "^2.13.2" - tiny-typed-emitter@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz" @@ -24944,7 +21600,7 @@ tmp@^0.1.0: dependencies: rimraf "^2.6.3" -tmp@^0.2.1, tmp@~0.2.1: +tmp@~0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz" integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== @@ -25026,13 +21682,6 @@ tough-cookie@^2.3.3, tough-cookie@~2.5.0: universalify "^0.2.0" url-parse "^1.5.3" -toxic@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/toxic/-/toxic-1.0.1.tgz" - integrity sha512-WI3rIGdcaKULYg7KVoB0zcjikqvcYYvcuT6D89bFPz2rVR0Rl0PK6x8/X62rtdLtBKIE985NzVf/auTtGegIIg== - dependencies: - lodash "^4.17.10" - tr46@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz" @@ -25045,11 +21694,6 @@ tr46@~0.0.3: resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -"traverse@>=0.3.0 <0.4": - version "0.3.9" - resolved "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz" - integrity sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ== - treeify@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz" @@ -25065,11 +21709,6 @@ trim-newlines@^3.0.0: resolved "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== -triple-beam@^1.3.0: - version "1.4.1" - resolved "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz" - integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== - truffle-flattener@^1.4.0: version "1.6.0" resolved "https://registry.npmjs.org/truffle-flattener/-/truffle-flattener-1.6.0.tgz" @@ -25147,11 +21786,6 @@ truffle@5.9.0: optionalDependencies: "@truffle/db" "^2.0.24" -ts-deepmerge@^2.0.1: - version "2.0.7" - resolved "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-2.0.7.tgz" - integrity sha512-3phiGcxPSSR47RBubQxPoZ+pqXsEsozLo4G4AlSrsMKTFg9TA3l+3he5BqpUi9wiuDbaHWXH/amlzQ49uEdXtg== - ts-essentials@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-1.0.4.tgz#ce3b5dade5f5d97cf69889c11bf7d2da8555b15a" @@ -25191,14 +21825,7 @@ ts-jest@^29.0.0: semver "^7.5.3" yargs-parser "^21.0.1" -ts-mockito@^2.6.1: - version "2.6.1" - resolved "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz" - integrity sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw== - dependencies: - lodash "^4.17.5" - -ts-node@8.10.2, ts-node@^8.3.0, ts-node@^8.4.1, ts-node@^8.5.4: +ts-node@8.10.2, ts-node@^8.5.4: version "8.10.2" resolved "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz" integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== @@ -25263,7 +21890,7 @@ tslib@1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" integrity sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ== -tslib@^1, tslib@^1.10.0, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@^1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -25273,7 +21900,7 @@ tslib@^2.0.0, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5 resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz" integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== -tslib@^2.0.1, tslib@^2.0.3, tslib@^2.3.0, tslib@^2.6.1, tslib@^2.6.2: +tslib@^2.0.3, tslib@^2.3.0, tslib@^2.6.1, tslib@^2.6.2: version "2.6.2" resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -25373,29 +22000,11 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel@0.0.6: - version "0.0.6" - resolved "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz" - integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== -tweetnacl@^1.0.1: - version "1.0.3" - resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz" - integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== - -tweetsodium@0.0.5: - version "0.0.5" - resolved "https://registry.npmjs.org/tweetsodium/-/tweetsodium-0.0.5.tgz" - integrity sha512-T3aXZtx7KqQbutTtBfn+P5By3HdBuB1eCoGviIrRJV2sXeToxv2X2cv5RvYqgG26PSnN5m3fYixds22Gkfd11w== - dependencies: - blakejs "^1.1.0" - tweetnacl "^1.0.1" - type-check@~0.3.2: version "0.3.2" resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz" @@ -25413,11 +22022,6 @@ type-fest@^0.18.0: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz" integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== -type-fest@^0.20.2: - 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== - type-fest@^0.21.3: version "0.21.3" resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" @@ -25443,11 +22047,6 @@ type-fest@^0.8.1: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-fest@^3.0.0: - version "3.13.1" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz" - integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g== - type-is@~1.6.18: version "1.6.18" resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" @@ -25632,22 +22231,12 @@ typescript@4.7.4: resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== -typescript@^3.6.4: - version "3.9.10" - resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz" - integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== - typical@^2.6.0, typical@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d" integrity sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg== -uc.micro@^1.0.1, uc.micro@^1.0.5: - version "1.0.6" - resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz" - integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== - -uglify-js@^3.1.4, uglify-js@^3.7.7: +uglify-js@^3.1.4: version "3.17.4" resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz" integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== @@ -25685,17 +22274,12 @@ unc-path-regex@^0.1.0: resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz" integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== -underscore@1.12.1: - version "1.12.1" - resolved "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz" - integrity sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw== - underscore@1.9.1: version "1.9.1" resolved "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz" integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== -underscore@>1.4.4, "underscore@>= 1.3.1", underscore@~1.13.2: +underscore@>1.4.4: version "1.13.6" resolved "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz" integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== @@ -25710,13 +22294,6 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz" - integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== - dependencies: - unique-slug "^2.0.0" - unique-filename@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz" @@ -25731,13 +22308,6 @@ unique-filename@^3.0.0: dependencies: unique-slug "^4.0.0" -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz" - integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== - dependencies: - imurmurhash "^0.1.4" - unique-slug@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz" @@ -25759,30 +22329,6 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - -universal-analytics@^0.4.16: - version "0.4.23" - resolved "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.23.tgz" - integrity sha512-lgMIH7XBI6OgYn1woDEmxhGdj8yDefMKg7GkWdeATAlQZFrMrNyxSkpDzY57iY0/6fdlzTbBV03OawvvzG+q7A== - dependencies: - debug "^4.1.1" - request "^2.88.2" - uuid "^3.0.0" - -universal-analytics@^0.5.3: - version "0.5.3" - resolved "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.5.3.tgz" - integrity sha512-HXSMyIcf2XTvwZ6ZZQLfxfViRm/yTGoRgDeTbojtq6rezeyKB0sTBcKH2fhddnteAHRcHiKgr/ACpbgjGOC6RQ== - dependencies: - debug "^4.3.1" - uuid "^8.0.0" - universal-user-agent@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz" @@ -25816,22 +22362,6 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -unzipper@^0.10.10: - version "0.10.14" - resolved "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz" - integrity sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g== - dependencies: - big-integer "^1.6.17" - binary "~0.3.0" - bluebird "~3.4.1" - buffer-indexof-polyfill "~1.0.0" - duplexer2 "~0.1.4" - fstream "^1.0.12" - graceful-fs "^4.2.2" - listenercount "~1.0.1" - readable-stream "~2.3.6" - setimmediate "~1.0.4" - upath@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz" @@ -25845,67 +22375,6 @@ update-browserslist-db@^1.0.10: escalade "^3.1.1" picocolors "^1.0.0" -update-notifier-cjs@^5.1.6: - version "5.1.6" - resolved "https://registry.npmjs.org/update-notifier-cjs/-/update-notifier-cjs-5.1.6.tgz" - integrity sha512-wgxdSBWv3x/YpMzsWz5G4p4ec7JWD0HCl8W6bmNB6E5Gwo+1ym5oN4hiXpLf0mPySVEJEIsYlkshnplkg2OP9A== - dependencies: - boxen "^5.0.0" - chalk "^4.1.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.4.0" - is-npm "^5.0.0" - is-yarn-global "^0.3.0" - isomorphic-fetch "^3.0.0" - pupa "^2.1.1" - registry-auth-token "^5.0.1" - registry-url "^5.1.0" - semver "^7.3.7" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - -update-notifier@^4.1.1: - version "4.1.3" - resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz" - integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A== - dependencies: - boxen "^4.2.0" - chalk "^3.0.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.3.1" - is-npm "^4.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.0.0" - pupa "^2.0.1" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - -update-notifier@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz" - integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== - dependencies: - boxen "^5.0.0" - chalk "^4.1.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.4.0" - is-npm "^5.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.1.0" - pupa "^2.1.1" - semver "^7.3.4" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - upper-case-first@^1.1.0, upper-case-first@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz" @@ -25930,11 +22399,6 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== -url-join@0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz" - integrity sha512-H6dnQ/yPAAVzMQRvEvyz01hhfQL5qRWSEt7BX8t9DqnPw9BjMb64fjIRq76Uvf1hkHp+mTZvEVJ5guXOT0Xqaw== - url-parse-lax@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz" @@ -26091,16 +22555,11 @@ uuid@8.3.2, uuid@^8.0.0, uuid@^8.3.0, uuid@^8.3.2: resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uuid@^3.0.0, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2, uuid@^3.3.3: +uuid@^3.2.1, uuid@^3.3.2, uuid@^3.3.3: version "3.4.0" resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^7.0.3: - version "7.0.3" - resolved "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz" - integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== - uuid@^9.0.0: version "9.0.0" resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz" @@ -26125,7 +22584,7 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" -valid-url@^1, valid-url@^1.0.9: +valid-url@^1.0.9: version "1.0.9" resolved "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz" integrity sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA== @@ -26197,14 +22656,6 @@ viem@~1.5.4: isomorphic-ws "5.0.0" ws "8.12.0" -vm2@^3.9.17: - version "3.9.19" - resolved "https://registry.npmjs.org/vm2/-/vm2-3.9.19.tgz" - integrity sha512-J637XF0DHDMV57R6JyVsTak7nIL8gy5KH4r1HiwWLf/4GBbb5MKL5y7LpmF4A8E2nR6XmzpmMFQ7V7ppPTmUQg== - dependencies: - acorn "^8.7.0" - acorn-walk "^8.2.0" - vscode-jsonrpc@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz" @@ -26289,16 +22740,6 @@ web3-bzz@1.2.2: swarm-js "0.1.39" underscore "1.9.1" -web3-bzz@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.3.6.tgz" - integrity sha512-ibHdx1wkseujFejrtY7ZyC0QxQ4ATXjzcNUpaLrvM6AEae8prUiyT/OloG9FWDgFD2CPLwzKwfSQezYQlANNlw== - dependencies: - "@types/node" "^12.12.6" - got "9.6.0" - swarm-js "^0.1.40" - underscore "1.12.1" - web3-bzz@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.1.tgz" @@ -26345,15 +22786,6 @@ web3-core-helpers@1.2.2: web3-eth-iban "1.2.2" web3-utils "1.2.2" -web3-core-helpers@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.3.6.tgz" - integrity sha512-nhtjA2ZbkppjlxTSwG0Ttu6FcPkVu1rCN5IFAOVpF/L0SEt+jy+O5l90+cjDq0jAYvlBwUwnbh2mR9hwDEJCNA== - dependencies: - underscore "1.12.1" - web3-eth-iban "1.3.6" - web3-utils "1.3.6" - web3-core-helpers@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.1.tgz" @@ -26406,18 +22838,6 @@ web3-core-method@1.2.2: web3-core-subscriptions "1.2.2" web3-utils "1.2.2" -web3-core-method@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.3.6.tgz" - integrity sha512-RyegqVGxn0cyYW5yzAwkPlsSEynkdPiegd7RxgB4ak1eKk2Cv1q2x4C7D2sZjeeCEF+q6fOkVmo2OZNqS2iQxg== - dependencies: - "@ethersproject/transactions" "^5.0.0-beta.135" - underscore "1.12.1" - web3-core-helpers "1.3.6" - web3-core-promievent "1.3.6" - web3-core-subscriptions "1.3.6" - web3-utils "1.3.6" - web3-core-method@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.1.tgz" @@ -26455,13 +22875,6 @@ web3-core-promievent@1.2.2: any-promise "1.3.0" eventemitter3 "3.1.2" -web3-core-promievent@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.3.6.tgz" - integrity sha512-Z+QzfyYDTXD5wJmZO5wwnRO8bAAHEItT1XNSPVb4J1CToV/I/SbF7CuF8Uzh2jns0Cm1109o666H7StFFvzVKw== - dependencies: - eventemitter3 "4.0.4" - web3-core-promievent@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.1.tgz" @@ -26498,18 +22911,6 @@ web3-core-requestmanager@1.2.2: web3-providers-ipc "1.2.2" web3-providers-ws "1.2.2" -web3-core-requestmanager@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.3.6.tgz" - integrity sha512-2rIaeuqeo7QN1Eex7aXP0ZqeteJEPWXYFS/M3r3LXMiV8R4STQBKE+//dnHJXoo2ctzEB5cgd+7NaJM8S3gPyA== - dependencies: - underscore "1.12.1" - util "^0.12.0" - web3-core-helpers "1.3.6" - web3-providers-http "1.3.6" - web3-providers-ipc "1.3.6" - web3-providers-ws "1.3.6" - web3-core-requestmanager@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.1.tgz" @@ -26558,15 +22959,6 @@ web3-core-subscriptions@1.2.2: underscore "1.9.1" web3-core-helpers "1.2.2" -web3-core-subscriptions@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.3.6.tgz" - integrity sha512-wi9Z9X5X75OKvxAg42GGIf81ttbNR2TxzkAsp1g+nnp5K8mBwgZvXrIsDuj7Z7gx72Y45mWJADCWjk/2vqNu8g== - dependencies: - eventemitter3 "4.0.4" - underscore "1.12.1" - web3-core-helpers "1.3.6" - web3-core-subscriptions@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.1.tgz" @@ -26621,19 +23013,6 @@ web3-core@1.2.2: web3-core-requestmanager "1.2.2" web3-utils "1.2.2" -web3-core@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-core/-/web3-core-1.3.6.tgz" - integrity sha512-gkLDM4T1Sc0T+HZIwxrNrwPg0IfWI0oABSglP2X5ZbBAYVUeEATA0o92LWV8BeF+okvKXLK1Fek/p6axwM/h3Q== - dependencies: - "@types/bn.js" "^4.11.5" - "@types/node" "^12.12.6" - bignumber.js "^9.0.0" - web3-core-helpers "1.3.6" - web3-core-method "1.3.6" - web3-core-requestmanager "1.3.6" - web3-utils "1.3.6" - web3-core@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-core/-/web3-core-1.8.1.tgz" @@ -26699,15 +23078,6 @@ web3-eth-abi@1.2.2: underscore "1.9.1" web3-utils "1.2.2" -web3-eth-abi@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.3.6.tgz" - integrity sha512-Or5cRnZu6WzgScpmbkvC6bfNxR26hqiKK4i8sMPFeTUABQcb/FU3pBj7huBLYbp9dH+P5W79D2MqwbWwjj9DoQ== - dependencies: - "@ethersproject/abi" "5.0.7" - underscore "1.12.1" - web3-utils "1.3.6" - web3-eth-abi@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.1.tgz" @@ -26769,23 +23139,6 @@ web3-eth-accounts@1.2.2: web3-core-method "1.2.2" web3-utils "1.2.2" -web3-eth-accounts@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.3.6.tgz" - integrity sha512-Ilr0hG6ONbCdSlVKffasCmNwftD5HsNpwyQASevocIQwHdTlvlwO0tb3oGYuajbKOaDzNTwXfz25bttAEoFCGA== - dependencies: - crypto-browserify "3.12.0" - eth-lib "0.2.8" - ethereumjs-common "^1.3.2" - ethereumjs-tx "^2.1.1" - scrypt-js "^3.0.1" - underscore "1.12.1" - uuid "3.3.2" - web3-core "1.3.6" - web3-core-helpers "1.3.6" - web3-core-method "1.3.6" - web3-utils "1.3.6" - web3-eth-accounts@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.1.tgz" @@ -26874,21 +23227,6 @@ web3-eth-contract@1.2.2: web3-eth-abi "1.2.2" web3-utils "1.2.2" -web3-eth-contract@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.3.6.tgz" - integrity sha512-8gDaRrLF2HCg+YEZN1ov0zN35vmtPnGf3h1DxmJQK5Wm2lRMLomz9rsWsuvig3UJMHqZAQKD7tOl3ocJocQsmA== - dependencies: - "@types/bn.js" "^4.11.5" - underscore "1.12.1" - web3-core "1.3.6" - web3-core-helpers "1.3.6" - web3-core-method "1.3.6" - web3-core-promievent "1.3.6" - web3-core-subscriptions "1.3.6" - web3-eth-abi "1.3.6" - web3-utils "1.3.6" - web3-eth-contract@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.1.tgz" @@ -26958,21 +23296,6 @@ web3-eth-ens@1.2.2: web3-eth-contract "1.2.2" web3-utils "1.2.2" -web3-eth-ens@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.3.6.tgz" - integrity sha512-n27HNj7lpSkRxTgSx+Zo7cmKAgyg2ElFilaFlUu/X2CNH23lXfcPm2bWssivH9z0ndhg0OyR4AYFZqPaqDHkJA== - dependencies: - content-hash "^2.5.2" - eth-ens-namehash "2.0.8" - underscore "1.12.1" - web3-core "1.3.6" - web3-core-helpers "1.3.6" - web3-core-promievent "1.3.6" - web3-eth-abi "1.3.6" - web3-eth-contract "1.3.6" - web3-utils "1.3.6" - web3-eth-ens@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.1.tgz" @@ -27041,14 +23364,6 @@ web3-eth-iban@1.2.2: bn.js "4.11.8" web3-utils "1.2.2" -web3-eth-iban@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.3.6.tgz" - integrity sha512-nfMQaaLA/zsg5W4Oy/EJQbs8rSs1vBAX6b/35xzjYoutXlpHMQadujDx2RerTKhSHqFXSJeQAfE+2f6mdhYkRQ== - dependencies: - bn.js "^4.11.9" - web3-utils "1.3.6" - web3-eth-iban@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.1.tgz" @@ -27099,18 +23414,6 @@ web3-eth-personal@1.2.2: web3-net "1.2.2" web3-utils "1.2.2" -web3-eth-personal@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.3.6.tgz" - integrity sha512-pOHU0+/h1RFRYoh1ehYBehRbcKWP4OSzd4F7mDljhHngv6W8ewMHrAN8O1ol9uysN2MuCdRE19qkRg5eNgvzFQ== - dependencies: - "@types/node" "^12.12.6" - web3-core "1.3.6" - web3-core-helpers "1.3.6" - web3-core-method "1.3.6" - web3-net "1.3.6" - web3-utils "1.3.6" - web3-eth-personal@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.1.tgz" @@ -27184,25 +23487,6 @@ web3-eth@1.2.2: web3-net "1.2.2" web3-utils "1.2.2" -web3-eth@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-eth/-/web3-eth-1.3.6.tgz" - integrity sha512-9+rnywRRpyX3C4hfsAQXPQh6vHh9XzQkgLxo3gyeXfbhbShUoq2gFVuy42vsRs//6JlsKdyZS7Z3hHPHz2wreA== - dependencies: - underscore "1.12.1" - web3-core "1.3.6" - web3-core-helpers "1.3.6" - web3-core-method "1.3.6" - web3-core-subscriptions "1.3.6" - web3-eth-abi "1.3.6" - web3-eth-accounts "1.3.6" - web3-eth-contract "1.3.6" - web3-eth-ens "1.3.6" - web3-eth-iban "1.3.6" - web3-eth-personal "1.3.6" - web3-net "1.3.6" - web3-utils "1.3.6" - web3-eth@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.1.tgz" @@ -27287,15 +23571,6 @@ web3-net@1.2.2: web3-core-method "1.2.2" web3-utils "1.2.2" -web3-net@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-net/-/web3-net-1.3.6.tgz" - integrity sha512-KhzU3wMQY/YYjyMiQzbaLPt2kut88Ncx2iqjy3nw28vRux3gVX0WOCk9EL/KVJBiAA/fK7VklTXvgy9dZnnipw== - dependencies: - web3-core "1.3.6" - web3-core-method "1.3.6" - web3-utils "1.3.6" - web3-net@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-net/-/web3-net-1.8.1.tgz" @@ -27416,14 +23691,6 @@ web3-providers-http@1.2.2: web3-core-helpers "1.2.2" xhr2-cookies "1.1.0" -web3-providers-http@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.3.6.tgz" - integrity sha512-OQkT32O1A06dISIdazpGLveZcOXhEo5cEX6QyiSQkiPk/cjzDrXMw4SKZOGQbbS1+0Vjizm1Hrp7O8Vp2D1M5Q== - dependencies: - web3-core-helpers "1.3.6" - xhr2-cookies "1.1.0" - web3-providers-http@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.1.tgz" @@ -27471,15 +23738,6 @@ web3-providers-ipc@1.2.2: underscore "1.9.1" web3-core-helpers "1.2.2" -web3-providers-ipc@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.3.6.tgz" - integrity sha512-+TVsSd2sSVvVgHG4s6FXwwYPPT91boKKcRuEFXqEfAbUC5t52XOgmyc2LNiD9LzPhed65FbV4LqICpeYGUvSwA== - dependencies: - oboe "2.1.5" - underscore "1.12.1" - web3-core-helpers "1.3.6" - web3-providers-ipc@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.1.tgz" @@ -27523,16 +23781,6 @@ web3-providers-ws@1.2.2: web3-core-helpers "1.2.2" websocket "github:web3-js/WebSocket-Node#polyfill/globalThis" -web3-providers-ws@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.3.6.tgz" - integrity sha512-bk7MnJf5or0Re2zKyhR3L3CjGululLCHXx4vlbc/drnaTARUVvi559OI5uLytc/1k5HKUUyENAxLvetz2G1dnQ== - dependencies: - eventemitter3 "4.0.4" - underscore "1.12.1" - web3-core-helpers "1.3.6" - websocket "^1.0.32" - web3-providers-ws@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.1.tgz" @@ -27609,16 +23857,6 @@ web3-shh@1.2.2: web3-core-subscriptions "1.2.2" web3-net "1.2.2" -web3-shh@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-shh/-/web3-shh-1.3.6.tgz" - integrity sha512-9zRo415O0iBslxBnmu9OzYjNErzLnzOsy+IOvSpIreLYbbAw0XkDWxv3SfcpKnTIWIACBR4AYMIxmmyi5iB3jw== - dependencies: - web3-core "1.3.6" - web3-core-method "1.3.6" - web3-core-subscriptions "1.3.6" - web3-net "1.3.6" - web3-shh@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.1.tgz" @@ -27700,20 +23938,6 @@ web3-utils@1.2.2: underscore "1.9.1" utf8 "3.0.0" -web3-utils@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.6.tgz" - integrity sha512-hHatFaQpkQgjGVER17gNx8u1qMyaXFZtM0y0XLGH1bzsjMPlkMPLRcYOrZ00rOPfTEuYFOdrpGOqZXVmGrMZRg== - dependencies: - bn.js "^4.11.9" - eth-lib "0.2.8" - ethereum-bloom-filters "^1.0.6" - ethjs-unit "0.1.6" - number-to-bn "1.7.0" - randombytes "^2.1.0" - underscore "1.12.1" - utf8 "3.0.0" - web3-utils@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.1.tgz" @@ -27824,19 +24048,6 @@ web3@1.2.2: web3-shh "1.2.2" web3-utils "1.2.2" -web3@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/web3/-/web3-1.3.6.tgz" - integrity sha512-jEpPhnL6GDteifdVh7ulzlPrtVQeA30V9vnki9liYlUvLV82ZM7BNOQJiuzlDePuE+jZETZSP/0G/JlUVt6pOA== - dependencies: - web3-bzz "1.3.6" - web3-core "1.3.6" - web3-eth "1.3.6" - web3-eth-personal "1.3.6" - web3-net "1.3.6" - web3-shh "1.3.6" - web3-utils "1.3.6" - web3@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/web3/-/web3-1.8.1.tgz" @@ -27894,20 +24105,6 @@ webidl-conversions@^4.0.2: resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -websocket-driver@>=0.5.1: - version "0.7.4" - resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - websocket@^1.0.28, websocket@^1.0.32: version "1.0.34" resolved "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz" @@ -27930,11 +24127,6 @@ websocket@^1.0.28, websocket@^1.0.32: typedarray-to-buffer "^3.1.5" yaeti "^0.0.6" -whatwg-fetch@3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz" - integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== - whatwg-fetch@>=0.10.0, whatwg-fetch@^3.4.1: version "3.6.2" resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz" @@ -28065,15 +24257,6 @@ window-size@^0.2.0: resolved "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz" integrity sha512-UD7d8HFA2+PZsbKyaOCEy8gMh1oDtHgJh1LfgjQ4zVXmYjAT/kvz3PueITKuqDiIXQe7yzpPnxX3lNc+AhQMyw== -winston-transport@^4.4.0, winston-transport@^4.5.0: - version "4.5.0" - resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz" - integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q== - dependencies: - logform "^2.3.2" - readable-stream "^3.6.0" - triple-beam "^1.3.0" - winston@2.x: version "2.4.7" resolved "https://registry.npmjs.org/winston/-/winston-2.4.7.tgz" @@ -28086,23 +24269,6 @@ winston@2.x: isstream "0.1.x" stack-trace "0.0.x" -winston@^3.0.0: - version "3.10.0" - resolved "https://registry.npmjs.org/winston/-/winston-3.10.0.tgz" - integrity sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g== - dependencies: - "@colors/colors" "1.5.0" - "@dabh/diagnostics" "^2.0.2" - async "^3.2.3" - is-stream "^2.0.0" - logform "^2.4.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - safe-stable-stringify "^2.3.1" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.5.0" - word-wrap@~1.2.3: version "1.2.4" resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz" @@ -28285,7 +24451,7 @@ ws@^5.1.1: dependencies: async-limiter "~1.0.0" -ws@^7.2.0, ws@^7.2.3: +ws@^7.2.0: version "7.5.9" resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== @@ -28300,11 +24466,6 @@ xdg-basedir@^3.0.0: resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz" integrity sha512-1Dly4xqlulvPD3fZUQJLY+FUIeqN3N2MM3uqe4rCJftAvOjFa3jFGfctOgluGx4ahPbUCsZkmJILiP0Vi4T6lQ== -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - xhr-request-promise@^0.1.2: version "0.1.3" resolved "https://registry.npmjs.org/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz" @@ -28347,7 +24508,7 @@ xhr@^2.0.4, xhr@^2.2.0, xhr@^2.3.3: parse-headers "^2.0.0" xtend "^4.0.0" -xml2js@0.5.0, xml2js@^0.5.0: +xml2js@0.5.0: version "0.5.0" resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz" integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA== @@ -28365,11 +24526,6 @@ xmlbuilder@~11.0.0: resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== -xmlcreate@^2.0.4: - version "2.0.4" - resolved "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz" - integrity sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg== - xmlhttprequest@*, xmlhttprequest@1.8.0: version "1.8.0" resolved "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz" @@ -28380,16 +24536,6 @@ xorshift@^1.1.1: resolved "https://registry.npmjs.org/xorshift/-/xorshift-1.2.0.tgz" integrity sha512-iYgNnGyeeJ4t6U11NpA/QiKy+PXn5Aa3Azg5qkwIFz1tBLllQrjjsk9yzD7IAK0naNU4JxdeDgqW9ov4u/hc4g== -xpath.js@~1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz" - integrity sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ== - -xregexp@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz" - integrity sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA== - xss@^1.0.8: version "1.0.14" resolved "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz" @@ -28430,11 +24576,6 @@ yaeti@^0.0.6: resolved "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz" integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug== -yallist@^2.0.0: - version "2.1.2" - resolved "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz" - integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== - yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" @@ -28450,11 +24591,6 @@ yaml@^1.10.0, yaml@^1.10.2: resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yaml@^2.2.1: - version "2.3.2" - resolved "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz" - integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg== - yargs-parser@13.1.2, yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz" @@ -28666,15 +24802,6 @@ yocto-queue@^0.1.0: resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zip-stream@^4.1.0: - version "4.1.1" - resolved "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz" - integrity sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ== - dependencies: - archiver-utils "^3.0.4" - compress-commons "^4.1.2" - readable-stream "^3.6.0" - zod@^3.21.4: version "3.22.2" resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.2.tgz#3add8c682b7077c05ac6f979fea6998b573e157b"